/*-------------------------------------------------------------------------
 *
 * initsplan.c
 *	  目标列表、资格、联接信息初始化例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/optimizer/plan/initsplan.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/inherit.h"
#include "optimizer/joininfo.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "parser/analyze.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"

/* 这些参数由GUC设置 */
int			from_collapse_limit;
int			join_collapse_limit;


/* 在 deconstruct_recurse 期间使用的 postponed_qual_list 的元素 */
typedef struct PostponedQual
{
	Node	   *qual;			/* 等待处理的 qual 子句 */
	Relids		relids;			/* 所引用的 baserels 集合 */
} PostponedQual;


static void fc_extract_lateral_references(PlannerInfo *fc_root, RelOptInfo *fc_brel,
									   Index fc_rtindex);
static List *fc_deconstruct_recurse(PlannerInfo *fc_root, Node *fc_jtnode,
								 bool fc_below_outer_join,
								 Relids *fc_qualscope, Relids *fc_inner_join_rels,
								 List **fc_postponed_qual_list);
static void fc_process_security_barrier_quals(PlannerInfo *fc_root,
										   int fc_rti, Relids fc_qualscope,
										   bool fc_below_outer_join);
static SpecialJoinInfo *fc_make_outerjoininfo(PlannerInfo *fc_root,
										   Relids fc_left_rels, Relids fc_right_rels,
										   Relids fc_inner_join_rels,
										   JoinType fc_jointype, List *fc_clause);
static void fc_compute_semijoin_info(PlannerInfo *fc_root, SpecialJoinInfo *fc_sjinfo,
								  List *fc_clause);
static void fc_distribute_qual_to_rels(PlannerInfo *fc_root, Node *fc_clause,
									bool fc_below_outer_join,
									JoinType fc_jointype,
									Index fc_security_level,
									Relids fc_qualscope,
									Relids fc_ojscope,
									Relids fc_outerjoin_nonnullable,
									List **fc_postponed_qual_list);
static bool fc_check_outerjoin_delay(PlannerInfo *fc_root, Relids *fc_relids_p,
								  Relids *fc_nullable_relids_p, bool fc_is_pushed_down);
static bool fc_check_equivalence_delay(PlannerInfo *fc_root,
									RestrictInfo *fc_restrictinfo);
static bool fc_check_redundant_nullability_qual(PlannerInfo *fc_root, Node *fc_clause);
static void fc_check_mergejoinable(RestrictInfo *fc_restrictinfo);
static void fc_check_hashjoinable(RestrictInfo *fc_restrictinfo);
static void fc_check_memoizable(RestrictInfo *fc_restrictinfo);


/*****************************************************************************
 *
 *	 JOIN TREES
 *
 *****************************************************************************/

/*
 * add_base_rels_to_query
 *
 *	  扫描查询的联合树，并为所有在联合树中出现的基关系（例如，表、子查询和函数 RTEs）
 *	  创建 baserel RelOptInfos。
 *
 * 初始调用必须将 root->parse->jointree 作为 jtnode 的值传递。内部，该函数会递归遍历联合树。
 *
 * 在此过程结束时，每个在查询中使用的非连接 RTE 应该有一个 baserel RelOptInfo。
 * 一些 baserels 可能是 appendrel 的父级，这将需要其他 "otherrel" RelOptInfos
 * 来表示其成员关系，但这些稍后会添加。
 */
void add_base_rels_to_query(PlannerInfo *fc_root, Node *fc_jtnode)
{
	if (fc_jtnode == NULL)
		return;
	if (IsA(fc_jtnode, RangeTblRef))
	{
		int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;

		(void) build_simple_rel(fc_root, fc_varno, NULL);
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		ListCell   *fc_l;

		foreach(fc_l, fc_f->fromlist)
			add_base_rels_to_query(fc_root, lfirst(fc_l));
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;

		add_base_rels_to_query(fc_root, fc_j->larg);
		add_base_rels_to_query(fc_root, fc_j->rarg);
	}
	else
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
}

/*
 * add_other_rels_to_query
 *	  为 appendrel baserels 的子项创建 "otherrel" RelOptInfos。
 *
 * 在此过程结束时，应该为查询将要扫描的所有关系都有 RelOptInfos。
 */
void add_other_rels_to_query(PlannerInfo *fc_root)
{
	int			fc_rti;

	for (fc_rti = 1; fc_rti < fc_root->simple_rel_array_size; fc_rti++)
	{
		RelOptInfo *fc_rel = fc_root->simple_rel_array[fc_rti];
		RangeTblEntry *fc_rte = fc_root->simple_rte_array[fc_rti];

		/* 可能会有空位对应于非基础关系的RTE */
		if (fc_rel == NULL)
			continue;

		/* 忽略任何已经添加的 "otherrels"。 */
		if (fc_rel->reloptkind != RELOPT_BASEREL)
			continue;

		/* 如果标记为可继承，则查找子项。 */
		if (fc_rte->inh)
			expand_inherited_rtentry(fc_root, fc_rel, fc_rte, fc_rti);
	}
}


/*****************************************************************************
 *
 *	 TARGET LISTS
 *
 *****************************************************************************/

/*
 * build_base_rel_tlists
 *	  为查询最终目标列表中所需的每个变量在适当的基关系中添加目标列表条目
 *	  （如果有的话，包含 HAVING 子句）。
 *
 * 我们将这些变量标记为 "relation 0" 所需，以确保它们会在所有连接计划步骤中传播上升。
 */
void build_base_rel_tlists(PlannerInfo *fc_root, List *fc_final_tlist)
{
	List	   *fc_tlist_vars = pull_var_clause((Node *) fc_final_tlist,
											 PVC_RECURSE_AGGREGATES |
											 PVC_RECURSE_WINDOWFUNCS |
											 PVC_INCLUDE_PLACEHOLDERS);

	if (fc_tlist_vars != NIL)
	{
		add_vars_to_targetlist(fc_root, fc_tlist_vars, bms_make_singleton(0), true);
		list_free(fc_tlist_vars);
	}

	/*
	 * 如果有 HAVING 子句，我们还需要它使用的变量。请注意，HAVING 可以包含 Aggrefs，但不能包含 WindowFuncs。
	 */
	if (fc_root->parse->havingQual)
	{
		List	   *fc_having_vars = pull_var_clause(fc_root->parse->havingQual,
												  PVC_RECURSE_AGGREGATES |
												  PVC_INCLUDE_PLACEHOLDERS);

		if (fc_having_vars != NIL)
		{
			add_vars_to_targetlist(fc_root, fc_having_vars,
								   bms_make_singleton(0), true);
			list_free(fc_having_vars);
		}
	}
}

/*
 * add_vars_to_targetlist
 *	  对于列表中出现的每个变量，如果尚未存在，则将其添加到拥有关系的目标列表中，并将该变量标记为
 *	  在指示的连接中需要（如果 where_needed 包含 "relation 0" 则为最终输出）。
 *
 *	  列表中还可能包含 PlaceHolderVars。这些不一定具有单一的拥有关系；我们将它们的 attr_needed 信息保存在
 *	  root->placeholder_list 中。如果 create_new_ph 为真，创建新的 PlaceHolderInfos 是可以的；
 *	  否则，PlaceHolderInfos 必须已经存在，我们只应更新它们的 ph_needed。
 *	  （在 deconstruct_jointree 开始之前，这应为真，之后则为假。）
 */
void add_vars_to_targetlist(PlannerInfo *fc_root, List *fc_vars,
					   Relids fc_where_needed, bool fc_create_new_ph)
{
	ListCell   *fc_temp;

	Assert(!bms_is_empty(fc_where_needed));

	foreach(fc_temp, fc_vars)
	{
		Node	   *fc_node = (Node *) lfirst(fc_temp);

		if (IsA(fc_node, Var))
		{
			Var		   *fc_var = (Var *) fc_node;
			RelOptInfo *fc_rel = find_base_rel(fc_root, fc_var->varno);
			int			fc_attno = fc_var->varattno;

			if (bms_is_subset(fc_where_needed, fc_rel->relids))
				continue;
			Assert(fc_attno >= fc_rel->min_attr && fc_attno <= fc_rel->max_attr);
			fc_attno -= fc_rel->min_attr;
			if (fc_rel->attr_needed[fc_attno] == NULL)
			{
				/* 变量尚未请求，因此添加到关系的目标列表 */
				/* XXX 这里需要 copyObject 吗？ */
				fc_rel->reltarget->exprs = lappend(fc_rel->reltarget->exprs,
												copyObject(fc_var));
				/* reltarget 成本和宽度将在稍后计算 */
			}
			fc_rel->attr_needed[fc_attno] = bms_add_members(fc_rel->attr_needed[fc_attno],
													  fc_where_needed);
		}
		else if (IsA(fc_node, PlaceHolderVar))
		{
			PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;
			PlaceHolderInfo *fc_phinfo = find_placeholder_info(fc_root, fc_phv,
															fc_create_new_ph);

			fc_phinfo->ph_needed = bms_add_members(fc_phinfo->ph_needed,
												fc_where_needed);
		}
		else
			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(fc_node));
	}
}


/*****************************************************************************
 *
 *	  LATERAL REFERENCES
 *
 *****************************************************************************/

/*
 * find_lateral_references
 *	  对于每个 LATERAL 子查询，提取其对当前查询级别的 Vars 和
 *	  PlaceHolderVars 的所有引用，并确保这些值将在评估子查询时可用。
 *
 * 尽管后续的规划步骤确保 Var/PHV 源关系在相对于 LATERAL 子查询的嵌套循环外部，但我们还需要确保
 * Vars/PHVs 传播到嵌套循环连接级别；这意味着需要为它们设置合适的 where_needed 值。
 *
 * 请注意，这仅处理未展平的 LATERAL 子查询中的旁路引用。当我们展平一个 LATERAL
 * 子查询时，其旁路引用在父查询中变为普通的 Vars，但如果它们需要被外连接强制为 NULL，
 * 而那些外连接并不将 LATERAL 子查询也设为 NULL，则可能需要将它们包装在 PlaceHolderVars 中。
 * 这一切会在其他地方处理。
 *
 * 这必须在 deconstruct_jointree 之前运行，因为它可能会导致创建 PlaceHolderInfos。
 */
void find_lateral_references(PlannerInfo *fc_root)
{
	Index		fc_rti;

	/* 如果查询中没有 LATERAL RTEs，我们不需要做任何事情 */
	if (!fc_root->hasLateralRTEs)
		return;

	/*
	 * 检查所有 baserels（此时关系数组已经设置）。
	 */
	for (fc_rti = 1; fc_rti < fc_root->simple_rel_array_size; fc_rti++)
	{
		RelOptInfo *fc_brel = fc_root->simple_rel_array[fc_rti];

		/* 可能会有空位对应于非基础关系的RTE */
		if (fc_brel == NULL)
			continue;

		Assert(fc_brel->relid == fc_rti); /* 对数组进行完整性检查 */

		
/*
		 * 这段代码看上去不太明显。我们忽略appendrel
		 * otherrels，只考虑它们的父级baserels。在一个包含
		 * LATERAL的UNION ALL子查询被拉升的情况下，实际上要在计划中的
		 * 是otherrel。然而，我们希望将它的所有横向引用标记为
		 * 由父级所需，因为用于连接计划目的的是父级的relid。
		 * 而父级的RTE将包含我们需要知道的所有横向引用，
		 * 因为被拉升的成员不过是原始RTE的子查询部分的一个副本。
		 * 我们可以访问父级的子节点，并将它们的引用转换回
		 * 父级的relid，但这会复杂得多，并没有真正的好处。
		 * （这里重要的是，子成员尚未进行任何处理，除了被拉升。）
		 * 类似地，在通过继承扩展创建的appendrels中，仅查看父级关系
		 * 就足够了。
		 */

		/* 忽略被视为“其他关系”的RTE */
		if (fc_brel->reloptkind != RELOPT_BASEREL)
			continue;

		fc_extract_lateral_references(fc_root, fc_brel, fc_rti);
	}
}

static void fc_extract_lateral_references(PlannerInfo *fc_root, RelOptInfo *fc_brel, Index fc_rtindex)
{
	RangeTblEntry *fc_rte = fc_root->simple_rte_array[fc_rtindex];
	List	   *fc_vars;
	List	   *fc_newvars;
	Relids		fc_where_needed;
	ListCell   *fc_lc;

	/* 如果不是LATERAL，则无法进行交叉引用 */
	if (!fc_rte->lateral)
		return;

	/* 获取适当的变量 */
	if (fc_rte->rtekind == RTE_RELATION)
		fc_vars = pull_vars_of_level((Node *) fc_rte->tablesample, 0);
	else if (fc_rte->rtekind == RTE_SUBQUERY)
		fc_vars = pull_vars_of_level((Node *) fc_rte->subquery, 1);
	else if (fc_rte->rtekind == RTE_FUNCTION)
		fc_vars = pull_vars_of_level((Node *) fc_rte->functions, 0);
	else if (fc_rte->rtekind == RTE_TABLEFUNC)
		fc_vars = pull_vars_of_level((Node *) fc_rte->tablefunc, 0);
	else if (fc_rte->rtekind == RTE_VALUES)
		fc_vars = pull_vars_of_level((Node *) fc_rte->values_lists, 0);
	else
	{
		Assert(false);
		return;					/* 保持编译器安静 */
	}

	if (fc_vars == NIL)
		return;					/* 无需操作 */

	/* 复制每个Var（或PlaceHolderVar）并调整以匹配我们的级别 */
	fc_newvars = NIL;
	foreach(fc_lc, fc_vars)
	{
		Node	   *fc_node = (Node *) lfirst(fc_lc);

		fc_node = copyObject(fc_node);
		if (IsA(fc_node, Var))
		{
			Var		   *fc_var = (Var *) fc_node;

			/* 调整很简单，因为它只是一个节点 */
			fc_var->varlevelsup = 0;
		}
		else if (IsA(fc_node, PlaceHolderVar))
		{
			PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;
			int			fc_levelsup = fc_phv->phlevelsup;

			/* 还需要更努力地调整包含的表达式 */
			if (fc_levelsup != 0)
				IncrementVarSublevelsUp(fc_node, -fc_levelsup, 0);

			/*
			 * 如果我们将PHV从一个子查询RTE中拉出，
			 * 其表达式需要预处理。然而，
			 * subquery_planner()已经为函数和值RTE中的
			 * 级别零PHVs完成了这项工作。
			 */
			if (fc_levelsup > 0)
				fc_phv->phexpr = preprocess_phv_expression(fc_root, fc_phv->phexpr);
		}
		else
			Assert(false);
		fc_newvars = lappend(fc_newvars, fc_node);
	}

	list_free(fc_vars);

	/*
	 * 我们将Vars标记为在LATERAL RTE中“需要”。这有点作弊：
	 * 更正式的方法是在LATERAL RTE与其源RTE的连接中将每个
	 * 标记为所需。但这样做有效，且比为每个Var计算单独的
	 * where_needed要简单得多。
	 */
	fc_where_needed = bms_make_singleton(fc_rtindex);

	/*
	 * 将Vars推入其源关系的targetlists，将PHVs推入
	 * root->placeholder_list。
	 */
	add_vars_to_targetlist(fc_root, fc_newvars, fc_where_needed, true);

	/* 记住为create_lateral_join_info准备的横向引用 */
	fc_brel->lateral_vars = fc_newvars;
}

/*
 * create_lateral_join_info
 *	  填充每个基础关系的direct_lateral_relids、lateral_relids
 *	  和lateral_referencers集合。
 *
 * 这必须在deconstruct_jointree之后运行，因为我们需要知道
 * PlaceHolderVars的最终ph_eval_at值。
 */
void create_lateral_join_info(PlannerInfo *fc_root)
{
	bool		fc_found_laterals = false;
	Index		fc_rti;
	ListCell   *fc_lc;

	/* 如果查询中没有 LATERAL RTEs，我们不需要做任何事情 */
	if (!fc_root->hasLateralRTEs)
		return;

	/*
	 * 检查所有 baserels（此时关系数组已经设置）。
	 */
	for (fc_rti = 1; fc_rti < fc_root->simple_rel_array_size; fc_rti++)
	{
		RelOptInfo *fc_brel = fc_root->simple_rel_array[fc_rti];
		Relids		fc_lateral_relids;

		/* 可能会有空位对应于非基础关系的RTE */
		if (fc_brel == NULL)
			continue;

		Assert(fc_brel->relid == fc_rti); /* 对数组进行完整性检查 */

		/* 忽略被视为“其他关系”的RTE */
		if (fc_brel->reloptkind != RELOPT_BASEREL)
			continue;

		fc_lateral_relids = NULL;

		/* 考虑每个横向引用的Var或PHV */
		foreach(fc_lc, fc_brel->lateral_vars)
		{
			Node	   *fc_node = (Node *) lfirst(fc_lc);

			if (IsA(fc_node, Var))
			{
				Var		   *fc_var = (Var *) fc_node;

				fc_found_laterals = true;
				fc_lateral_relids = bms_add_member(fc_lateral_relids,
												fc_var->varno);
			}
			else if (IsA(fc_node, PlaceHolderVar))
			{
				PlaceHolderVar *fc_phv = (PlaceHolderVar *) fc_node;
				PlaceHolderInfo *fc_phinfo = find_placeholder_info(fc_root, fc_phv,
																false);

				fc_found_laterals = true;
				fc_lateral_relids = bms_add_members(fc_lateral_relids,
												 fc_phinfo->ph_eval_at);
			}
			else
				Assert(false);
		}

		/* 我们现在拥有来自该关系的所有简单横向引用 */
		fc_brel->direct_lateral_relids = fc_lateral_relids;
		fc_brel->lateral_relids = bms_copy(fc_lateral_relids);
	}

	/*
	 * 现在检查 PlaceHolderVars 中的横向引用，并标记它们的
	 * eval_at 关系为与源关系具有横向引用。
	 *
	 * 对于预计在基关系处被评估的 PHV，将其源标记为基关系的直接
	 * 横向依赖关系（添加到上面记录的那些）。如果它预计在连接处被评估,
	 * 则将其源标记为连接中每个基关系的间接横向依赖关系，
	 * 即将它们放入 lateral_relids 中，但不直接放入
	 * lateral_relids。这是合适的，因为我们不能将任何这样的基关系放在
	 * PHV 的一个横向依赖关系的连接外部，而另一方面，我们
	 * 也不能直接将其连接到依赖关系。
	 */
	foreach(fc_lc, fc_root->placeholder_list)
	{
		PlaceHolderInfo *fc_phinfo = (PlaceHolderInfo *) lfirst(fc_lc);
		Relids		fc_eval_at = fc_phinfo->ph_eval_at;
		int			fc_varno;

		if (fc_phinfo->ph_lateral == NULL)
			continue;			/* PHV 在没有横向引用时不有趣 */

		fc_found_laterals = true;

		if (bms_get_singleton_member(fc_eval_at, &fc_varno))
		{
			/* 评估站点是一个基础关系 */
			RelOptInfo *fc_brel = find_base_rel(fc_root, fc_varno);

			fc_brel->direct_lateral_relids =
				bms_add_members(fc_brel->direct_lateral_relids,
								fc_phinfo->ph_lateral);
			fc_brel->lateral_relids =
				bms_add_members(fc_brel->lateral_relids,
								fc_phinfo->ph_lateral);
		}
		else
		{
			/* 评估站点是一个连接 */
			fc_varno = -1;
			while ((fc_varno = bms_next_member(fc_eval_at, fc_varno)) >= 0)
			{
				RelOptInfo *fc_brel = find_base_rel(fc_root, fc_varno);

				fc_brel->lateral_relids = bms_add_members(fc_brel->lateral_relids,
													   fc_phinfo->ph_lateral);
			}
		}
	}

	/*
	 * 如果我们没有找到实际的横向引用，那么就完成了；但是重置
	 * hasLateralRTEs 标志以避免后面的无用工作。
	 */
	if (!fc_found_laterals)
	{
		fc_root->hasLateralRTEs = false;
		return;
	}

	/*
	 * 计算横向关系集的传递闭包，以便它们描述直接和间接的横向引用。
	 * 如果关系 X 横向引用 Y，并且 Y 横向引用 Z，那么我们必须在一个嵌套循环中扫描
	 * X 和 Z，因此在所有意图和目的上，X 也横向依赖于 Z。
	 *
	 * 这个代码实质上是沃尔沙尔算法的传递闭包。
	 * 外部循环考虑每个基础关系，并将其横向依赖传播到那些
	 * 对它有横向依赖的基础关系。
	 */
	for (fc_rti = 1; fc_rti < fc_root->simple_rel_array_size; fc_rti++)
	{
		RelOptInfo *fc_brel = fc_root->simple_rel_array[fc_rti];
		Relids		fc_outer_lateral_relids;
		Index		fc_rti2;

		if (fc_brel == NULL || fc_brel->reloptkind != RELOPT_BASEREL)
			continue;

		/* 如果没有横向引用，则不需要进一步考虑基础关系 */
		fc_outer_lateral_relids = fc_brel->lateral_relids;
		if (fc_outer_lateral_relids == NULL)
			continue;

		/* 否则扫描所有基础关系 */
		for (fc_rti2 = 1; fc_rti2 < fc_root->simple_rel_array_size; fc_rti2++)
		{
			RelOptInfo *fc_brel2 = fc_root->simple_rel_array[fc_rti2];

			if (fc_brel2 == NULL || fc_brel2->reloptkind != RELOPT_BASEREL)
				continue;

			/* 如果 brel2 对 brel 有横向引用，则传播 brel 的引用 */
			if (bms_is_member(fc_rti, fc_brel2->lateral_relids))
				fc_brel2->lateral_relids = bms_add_members(fc_brel2->lateral_relids,
														fc_outer_lateral_relids);
		}
	}

	/*
	 * 现在我们已经识别了所有横向引用，标记每个基础关系
	 * 具有横向引用它的关系的 relid 集（可能是间接的）——也就是，横向关系的反向映射。
	 */
	for (fc_rti = 1; fc_rti < fc_root->simple_rel_array_size; fc_rti++)
	{
		RelOptInfo *fc_brel = fc_root->simple_rel_array[fc_rti];
		Relids		fc_lateral_relids;
		int			fc_rti2;

		if (fc_brel == NULL || fc_brel->reloptkind != RELOPT_BASEREL)
			continue;

		/* 对于没有横向引用的关系无需做任何事情 */
		fc_lateral_relids = fc_brel->lateral_relids;
		if (fc_lateral_relids == NULL)
			continue;

		/*
		 * 我们不应该破坏不变性，即当为空时 lateral_relids
		 * 正好是 NULL。
		 */
		Assert(!bms_is_empty(fc_lateral_relids));

		/* 此外，没有关系应该对自身有横向依赖 */
		Assert(!bms_is_member(fc_rti, fc_lateral_relids));

		/* 标记此关系的引用 */
		fc_rti2 = -1;
		while ((fc_rti2 = bms_next_member(fc_lateral_relids, fc_rti2)) >= 0)
		{
			RelOptInfo *fc_brel2 = fc_root->simple_rel_array[fc_rti2];

			Assert(fc_brel2 != NULL && fc_brel2->reloptkind == RELOPT_BASEREL);
			fc_brel2->lateral_referencers =
				bms_add_member(fc_brel2->lateral_referencers, fc_rti);
		}
	}
}


/*****************************************************************************
 *
 *	  连接树处理
 *
 *****************************************************************************/

/*
 * deconstruct_jointree
 *	  递归扫描查询的连接树以查找 WHERE 和 JOIN/ON 资格
 *	  子句，并将这些子句添加到基础 RelOptInfos 的适当 restrictinfo 和 joininfo
 *	  列表中。此外，对于查询树中出现的任何外连接，
 *	  将 SpecialJoinInfo 节点添加到 root->join_info_list中。
 *	  返回一个 "joinlist" 数据结构，显示 make_one_rel() 需要做的
 *	  连接顺序决策。
 *
 * "joinlist" 结果是一个项的列表，这些项是 RangeTblRef
 * 连接树节点或子连接列表。所有在同一级别的 joinlist 项
 * 必须按 make_one_rel() 确定的顺序连接
 * （请注意，合法的顺序可能受到 SpecialJoinInfo 节点的约束）。
 * 子连接列表代表一个需要单独计划的子问题。目前
 * 子连接列表仅来自 FULL OUTER JOIN 或连接折叠限制或
 * from_collapse_limit 停止时。
 *
 * 注意：在处理内连接时，适合让资格子句
 * 在提到的所有变量可用的最低级别进行评估。
 * 然而，我们不能将资格推到外连接的可空端，因为资格可能会消除匹配行，并导致连接错误地输出 NULL 行。
 * 因此，我们人为地将此外连接的最小关系添加到
 * 其上出现的子句的 required_relids 中。
 * 这迫使这些子句被延迟到外连接的应用（甚至可能在连接树的更高层）。
 */
List * deconstruct_jointree(PlannerInfo *fc_root)
{
	List	   *fc_result;
	Relids		fc_qualscope;
	Relids		fc_inner_join_rels;
	List	   *fc_postponed_qual_list = NIL;

	/* 在连接树顶部开始递归 */
	Assert(fc_root->parse->jointree != NULL &&
		   IsA(fc_root->parse->jointree, FromExpr));

	/* 随着我们扫描连接树填充此内容 */
	fc_root->nullable_baserels = NULL;

	fc_result = fc_deconstruct_recurse(fc_root, (Node *) fc_root->parse->jointree, false,
								 &fc_qualscope, &fc_inner_join_rels,
								 &fc_postponed_qual_list);

	/* 不应该有任何剩余的资格 */
	Assert(fc_postponed_qual_list == NIL);

	return fc_result;
}

/*
 * deconstruct_recurse
 *	  deconstruct_jointree 处理的一个递归级别。
 *
 * 输入：
 *	jtnode 是要检查的连接树节点
 *	below_outer_join 如果该节点在
 *		高层外连接的可空部分，则为 true。
 * 输出：
 *	*qualscope 得到此连接树节点中句法包含的基础 Relids 集
 *		（不要修改或释放此内容，因为它可能也会被 RestrictInfo 和 SpecialJoinInfo 节点指向）
 *	*inner_join_rels 得到在此连接树节点及以下的内连接中
 *		句法包含的基础 Relids 集（也不要修改或释放此）
 *	*postponed_qual_list 是 PostponedQual 结构的列表，
 *		如果它们属于更高的连接级别，则可以将资格添加到其中
 *	返回值是此连接树节点的适当连接列表
 *
 * 此外，将为外连接向 root->join_info_list 添加条目。
 */
static List * fc_deconstruct_recurse(PlannerInfo *fc_root, Node *fc_jtnode, bool fc_below_outer_join,
					Relids *fc_qualscope, Relids *fc_inner_join_rels,
					List **fc_postponed_qual_list)
{
	List	   *fc_joinlist;

	if (fc_jtnode == NULL)
	{
		*fc_qualscope = NULL;
		*fc_inner_join_rels = NULL;
		return NIL;
	}
	if (IsA(fc_jtnode, RangeTblRef))
	{
		int			fc_varno = ((RangeTblRef *) fc_jtnode)->rtindex;

		/* qualscope 仅为一个 RTE */
		*fc_qualscope = bms_make_singleton(fc_varno);
		/* 处理附加到 RTE 的任何 securityQuals */
		if (fc_root->qual_security_level > 0)
			fc_process_security_barrier_quals(fc_root,
										   fc_varno,
										   *fc_qualscope,
										   fc_below_outer_join);
		/* 单个基础关系不产生内连接 */
		*fc_inner_join_rels = NULL;
		fc_joinlist = list_make1(fc_jtnode);
	}
	else if (IsA(fc_jtnode, FromExpr))
	{
		FromExpr   *fc_f = (FromExpr *) fc_jtnode;
		List	   *fc_child_postponed_quals = NIL;
		int			fc_remaining;
		ListCell   *fc_l;

		/*
		 * 首先，递归处理子连接。当连接列表不会超过
		 * from_collapse_limit 成员时，我们将子问题折叠成
		 * 一个单一连接列表。此外，总是折叠包含一个元素的
		 * 子问题，因为这无论如何不会延长连接列表。
		 */
		*fc_qualscope = NULL;
		*fc_inner_join_rels = NULL;
		fc_joinlist = NIL;
		fc_remaining = list_length(fc_f->fromlist);
		foreach(fc_l, fc_f->fromlist)
		{
			Relids		fc_sub_qualscope;
			List	   *fc_sub_joinlist;
			int			fc_sub_members;

			fc_sub_joinlist = fc_deconstruct_recurse(fc_root, lfirst(fc_l),
											   fc_below_outer_join,
											   &fc_sub_qualscope,
											   fc_inner_join_rels,
											   &fc_child_postponed_quals);
			*fc_qualscope = bms_add_members(*fc_qualscope, fc_sub_qualscope);
			fc_sub_members = list_length(fc_sub_joinlist);
			fc_remaining--;
			if (fc_sub_members <= 1 ||
				list_length(fc_joinlist) + fc_sub_members + fc_remaining <= from_collapse_limit)
				fc_joinlist = list_concat(fc_joinlist, fc_sub_joinlist);
			else
				fc_joinlist = lappend(fc_joinlist, fc_sub_joinlist);
		}

		
/*
		 * 具有多个列表元素的 FROM 是一个内连接，包含所有下面的内容，因此我们应该报告 inner_join_rels = qualscope。如果恰好只有一个元素，我们应该（并且已经）报告其 inner_join_rels。如果没有元素（这仍然可能吗？），循环前的初始化已经修复了它。
		 */
		if (list_length(fc_f->fromlist) > 1)
			*fc_inner_join_rels = *fc_qualscope;

		/*
		 * 尝试处理子节点延迟的任何 quals。如果它们需要进一步延迟，将它们添加到我的输出 postponed_qual_list 中。
		 */
		foreach(fc_l, fc_child_postponed_quals)
		{
			PostponedQual *fc_pq = (PostponedQual *) lfirst(fc_l);

			if (bms_is_subset(fc_pq->relids, *fc_qualscope))
				fc_distribute_qual_to_rels(fc_root, fc_pq->qual,
										fc_below_outer_join, JOIN_INNER,
										fc_root->qual_security_level,
										*fc_qualscope, NULL, NULL,
										NULL);
			else
				*fc_postponed_qual_list = lappend(*fc_postponed_qual_list, fc_pq);
		}

		/*
		 * 现在处理顶级 quals。
		 */
		foreach(fc_l, (List *) fc_f->quals)
		{
			Node	   *fc_qual = (Node *) lfirst(fc_l);

			fc_distribute_qual_to_rels(fc_root, fc_qual,
									fc_below_outer_join, JOIN_INNER,
									fc_root->qual_security_level,
									*fc_qualscope, NULL, NULL,
									fc_postponed_qual_list);
		}
	}
	else if (IsA(fc_jtnode, JoinExpr))
	{
		JoinExpr   *fc_j = (JoinExpr *) fc_jtnode;
		List	   *fc_child_postponed_quals = NIL;
		Relids		fc_leftids,
					fc_rightids,
					fc_left_inners,
					fc_right_inners,
					fc_nonnullable_rels,
					fc_nullable_rels,
					fc_ojscope;
		List	   *fc_leftjoinlist,
				   *fc_rightjoinlist;
		List	   *fc_my_quals;
		SpecialJoinInfo *fc_sjinfo;
		ListCell   *fc_l;

		/*
		 * 这里的操作顺序微妙且至关重要。首先我们递归处理子 JOIN。它们的连接条件将被放置，而不考虑这一层是否是外连接，这是正确的。然后我们放置自己的连接条件，在任何情况下都受限于较低的外连接，如果这是外连接，并且它们提及外侧，则被迫放置在这一层。最后，如果这是一个外连接，我们为连接创建一个 join_info_list 条目。这将防止在连接树中使用这些关系的上层条件被推到这一层以下。（不过，上层条件被推到外侧是可以的。）
		 */
		switch (fc_j->jointype)
		{
			case JOIN_INNER:
				fc_leftjoinlist = fc_deconstruct_recurse(fc_root, fc_j->larg,
												   fc_below_outer_join,
												   &fc_leftids, &fc_left_inners,
												   &fc_child_postponed_quals);
				fc_rightjoinlist = fc_deconstruct_recurse(fc_root, fc_j->rarg,
													fc_below_outer_join,
													&fc_rightids, &fc_right_inners,
													&fc_child_postponed_quals);
				*fc_qualscope = bms_union(fc_leftids, fc_rightids);
				*fc_inner_join_rels = *fc_qualscope;
				/* 内连接对 quals 没有添加限制 */
				fc_nonnullable_rels = NULL;
				/* 并且它也不会强制任何东西为 null */
				fc_nullable_rels = NULL;
				break;
			case JOIN_LEFT:
			case JOIN_ANTI:
				fc_leftjoinlist = fc_deconstruct_recurse(fc_root, fc_j->larg,
												   fc_below_outer_join,
												   &fc_leftids, &fc_left_inners,
												   &fc_child_postponed_quals);
				fc_rightjoinlist = fc_deconstruct_recurse(fc_root, fc_j->rarg,
													true,
													&fc_rightids, &fc_right_inners,
													&fc_child_postponed_quals);
				*fc_qualscope = bms_union(fc_leftids, fc_rightids);
				*fc_inner_join_rels = bms_union(fc_left_inners, fc_right_inners);
				fc_nonnullable_rels = fc_leftids;
				fc_nullable_rels = fc_rightids;
				break;
			case JOIN_SEMI:
				fc_leftjoinlist = fc_deconstruct_recurse(fc_root, fc_j->larg,
												   fc_below_outer_join,
												   &fc_leftids, &fc_left_inners,
												   &fc_child_postponed_quals);
				fc_rightjoinlist = fc_deconstruct_recurse(fc_root, fc_j->rarg,
													fc_below_outer_join,
													&fc_rightids, &fc_right_inners,
													&fc_child_postponed_quals);
				*fc_qualscope = bms_union(fc_leftids, fc_rightids);
				*fc_inner_join_rels = bms_union(fc_left_inners, fc_right_inners);
				/* 半连接对 quals 没有添加限制 */
				fc_nonnullable_rels = NULL;

				/*
				 * 理论上，一个半连接会使右侧无效；但由于
				 * 在连接上方无法访问右侧，这无关紧要
				 * 我们不需要考虑它。
				 */
				fc_nullable_rels = NULL;
				break;
			case JOIN_FULL:
				fc_leftjoinlist = fc_deconstruct_recurse(fc_root, fc_j->larg,
												   true,
												   &fc_leftids, &fc_left_inners,
												   &fc_child_postponed_quals);
				fc_rightjoinlist = fc_deconstruct_recurse(fc_root, fc_j->rarg,
													true,
													&fc_rightids, &fc_right_inners,
													&fc_child_postponed_quals);
				*fc_qualscope = bms_union(fc_leftids, fc_rightids);
				*fc_inner_join_rels = bms_union(fc_left_inners, fc_right_inners);
				/* 每一侧都是外部和内部 */
				fc_nonnullable_rels = *fc_qualscope;
				fc_nullable_rels = *fc_qualscope;
				break;
			default:
				/* 在 reduce_outer_joins() 过程中消除了 JOIN_RIGHT */
				elog(ERROR, "unrecognized join type: %d",
					 (int) fc_j->jointype);
				fc_nonnullable_rels = NULL;	/* 保持编译器安静 */
				fc_nullable_rels = NULL;
				fc_leftjoinlist = fc_rightjoinlist = NIL;
				break;
		}

		/* 报告将在连接树中任何地方被置为无效的所有关系 */
		fc_root->nullable_baserels = bms_add_members(fc_root->nullable_baserels,
												  fc_nullable_rels);

		/*
		 * 尝试处理任何被子节点推迟的条件。如果它们需要
		 * 进一步推迟，将它们添加到我的输出推迟条件列表中。
		 * 现在可以处理的条件必须包含在我的条件中，以便
		 * 在 make_outerjoininfo 中得到妥善处理。
		 */
		fc_my_quals = NIL;
		foreach(fc_l, fc_child_postponed_quals)
		{
			PostponedQual *fc_pq = (PostponedQual *) lfirst(fc_l);

			if (bms_is_subset(fc_pq->relids, *fc_qualscope))
				fc_my_quals = lappend(fc_my_quals, fc_pq->qual);
			else
			{
				/*
				 * 我们不应该在外连接后推迟任何条件。
				 * 如果这个断言触发了，pull_up_subqueries() 出错了。
				 */
				Assert(fc_j->jointype == JOIN_INNER);
				*fc_postponed_qual_list = lappend(*fc_postponed_qual_list, fc_pq);
			}
		}
		fc_my_quals = list_concat(fc_my_quals, (List *) fc_j->quals);

		/*
		 * 对于一个外连接，现在形成 SpecialJoinInfo，因为我们需要外连接的
		 * 语义范围（ojscope）来传递给 distribute_qual_to_rels。但
		 * 我们还不能将其添加到 join_info_list 中，因为我们不希望
		 * distribute_qual_to_rels 认为它在我们下方是一个外连接。
		 *
		 * 半连接有点混合：我们构建一个 SpecialJoinInfo，但我们
		 * 希望 ojscope = NULL 供 distribute_qual_to_rels 使用。
		 */
		if (fc_j->jointype != JOIN_INNER)
		{
			fc_sjinfo = fc_make_outerjoininfo(fc_root,
										fc_leftids, fc_rightids,
										*fc_inner_join_rels,
										fc_j->jointype,
										fc_my_quals);
			if (fc_j->jointype == JOIN_SEMI)
				fc_ojscope = NULL;
			else
				fc_ojscope = bms_union(fc_sjinfo->min_lefthand,
									fc_sjinfo->min_righthand);
		}
		else
		{
			fc_sjinfo = NULL;
			fc_ojscope = NULL;
		}

		/* 处理连接的条件子句 */
		foreach(fc_l, fc_my_quals)
		{
			Node	   *fc_qual = (Node *) lfirst(fc_l);

			fc_distribute_qual_to_rels(fc_root, fc_qual,
									fc_below_outer_join, fc_j->jointype,
									fc_root->qual_security_level,
									*fc_qualscope,
									fc_ojscope, fc_nonnullable_rels,
									fc_postponed_qual_list);
		}

		/* 现在我们可以将 SpecialJoinInfo 添加到 join_info_list */
		if (fc_sjinfo)
		{
			fc_root->join_info_list = lappend(fc_root->join_info_list, fc_sjinfo);
			/* 每次这样做时，重新检查占位符评估级别 */
			update_placeholder_eval_levels(fc_root, fc_sjinfo);
		}

		/*
		 * 最后，计算输出连接列表。我们将子问题折叠在一起，
		 * 除非在 FULL JOIN 或者连接折叠限制将被
		 * 超出时。
		 */
		if (fc_j->jointype == JOIN_FULL)
		{
			/* 精确地在这个节点强制连接顺序 */
			fc_joinlist = list_make1(list_make2(fc_leftjoinlist, fc_rightjoinlist));
		}
		else if (list_length(fc_leftjoinlist) + list_length(fc_rightjoinlist) <=
				 join_collapse_limit)
		{
			/* 可以结合子问题 */
			fc_joinlist = list_concat(fc_leftjoinlist, fc_rightjoinlist);
		}
		else
		{
			/* 不能结合，但不需要在这里强制连接顺序 */
			Node	   *fc_leftpart,
					   *fc_rightpart;

			/* 避免创建无用的 1 元素子列表 */
			if (list_length(fc_leftjoinlist) == 1)
				fc_leftpart = (Node *) linitial(fc_leftjoinlist);
			else
				fc_leftpart = (Node *) fc_leftjoinlist;
			if (list_length(fc_rightjoinlist) == 1)
				fc_rightpart = (Node *) linitial(fc_rightjoinlist);
			else
				fc_rightpart = (Node *) fc_rightjoinlist;
			fc_joinlist = list_make2(fc_leftpart, fc_rightpart);
		}
	}
	else
	{
		elog(ERROR, "unrecognized node type: %d",
			 (int) nodeTag(fc_jtnode));
		fc_joinlist = NIL;			/* 保持编译器安静 */
	}
	return fc_joinlist;
}

/*
 * process_security_barrier_quals
 *	  将安全屏障条件转移到关系的 baserestrictinfo 列表中。
 *
 * 重写器将任何相关的安全屏障条件放入 RTE 的
 * securityQuals 字段，但现在是将它们复制到关系的
 * baserestrictinfo 中的时候了。
 *
 * 在继承情况下，我们只考虑附加到父关系的条件；
 * 它们对所有子关系都有效，因此在诸如等价类创建等情况下
 * 考虑它们是可以的。附加到单个子关系的条件将在路径
 * 创建期间处理。
 */
static void fc_process_security_barrier_quals(PlannerInfo *fc_root,
							   int fc_rti, Relids fc_qualscope,
							   bool fc_below_outer_join)
{
	RangeTblEntry *fc_rte = fc_root->simple_rte_array[fc_rti];
	Index		fc_security_level = 0;
	ListCell   *fc_lc;

	/*
	 * 安全条件列表的每个元素都已预处理为一个
	 * 隐式地 AND 组合的条件列表。给定子列表中的所有
	 * 条件应获得相同的安全级别，但连续的子列表获得
	 * 更高的级别。
	 */
	foreach(fc_lc, fc_rte->securityQuals)
	{
		List	   *fc_qualset = (List *) lfirst(fc_lc);
		ListCell   *fc_lc2;

		foreach(fc_lc2, fc_qualset)
		{
			Node	   *fc_qual = (Node *) lfirst(fc_lc2);

			/*
			 * 我们在传递 ojscope = qualscope 时有点作弊，而不是
			 * 更合逻辑的 NULL 值。这样做的唯一效果
			 * 是强制无变量的条件在关系上评估，而不是被
			 * 推到树的顶端，我们不希望这样。
			 */
			fc_distribute_qual_to_rels(fc_root, fc_qual,
									fc_below_outer_join,
									JOIN_INNER,
									fc_security_level,
									fc_qualscope,
									fc_qualscope,
									NULL,
									NULL);
		}
		fc_security_level++;
	}

	/* 断言 qual_security_level 高于我们刚刚使用的任何东西 */
	Assert(fc_security_level <= fc_root->qual_security_level);
}

/*
 * make_outerjoininfo
 *	  为当前外连接构建一个 SpecialJoinInfo
 *
 * 输入：
 *	left_rels: 连接外侧语法上的基本 Relids
 *	right_rels: 连接内侧语法上的基本 Relids
 *	inner_join_rels: 参与此连接下方的内连接的基本 Relids
 *	jointype: 它表示的内容（必须始终为 LEFT, FULL, SEMI 或 ANTI）
 *	clause: 外连接的连接条件（以隐式 AND 格式）
 *
 * 该节点最终应附加到 root->join_info_list，但我们
 * 在此不这样做。
 *
 * 注意：我们假定此函数是自下而上调用的，以便
 * root->join_info_list 已包含所有在语法上低于此
 * 的外连接的条目。
 */
static SpecialJoinInfo * fc_make_outerjoininfo(PlannerInfo *fc_root,
				   Relids fc_left_rels, Relids fc_right_rels,
				   Relids fc_inner_join_rels,
				   JoinType fc_jointype, List *fc_clause)
{
	SpecialJoinInfo *fc_sjinfo = makeNode(SpecialJoinInfo);
	Relids		fc_clause_relids;
	Relids		fc_strict_relids;
	Relids		fc_min_lefthand;
	Relids		fc_min_righthand;
	ListCell   *fc_l;

	/*
	 * 我们这里不应该看到 RIGHT JOIN，因为左/右在之前已被调换
	 */
	Assert(fc_jointype != JOIN_INNER);
	Assert(fc_jointype != JOIN_RIGHT);

	/*
	 * 目前执行器无法支持位于外连接可空侧的
	 * 关系的 FOR [KEY] UPDATE/SHARE 标记。(无论如何这有点
	 * 不清楚，这意味着什么：当从可空关系的无元素生成结果
	 * 行时，我们应该标记什么？) 因此，如果任何
	 * 可空关系是 FOR [KEY] UPDATE/SHARE，则发出警告。
	 *
	 * 你可能会问，为什么这个测试没有在解析器的上游做。
	 * 这是因为解析器没有足够的信息——考虑
	 * 应用于视图的 FOR UPDATE。在重写和扁平化之后，
	 * 我们才知道视图是否包含外连接。
	 *
	 * 我们在这里使用原始 RowMarkClause 列表；PlanRowMark
	 * 列表会列出所有内容。
	 */
	foreach(fc_l, fc_root->parse->rowMarks)
	{
		RowMarkClause *fc_rc = (RowMarkClause *) lfirst(fc_l);

		if (bms_is_member(fc_rc->rti, fc_right_rels) ||
			(fc_jointype == JOIN_FULL && bms_is_member(fc_rc->rti, fc_left_rels)))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			/*------
			 翻译者：%s 是一个 SQL 行锁定子句，例如 FOR UPDATE */
					 errmsg("%s cannot be applied to the nullable side of an outer join",
							LCS_asString(fc_rc->strength))));
	}

	fc_sjinfo->syn_lefthand = fc_left_rels;
	fc_sjinfo->syn_righthand = fc_right_rels;
	fc_sjinfo->jointype = fc_jointype;
	/* 这始终开始为假 */
	fc_sjinfo->delay_upper_joins = false;

	fc_compute_semijoin_info(fc_root, fc_sjinfo, fc_clause);

	/* 如果是完全连接，则无需非常聪明 */
	if (fc_jointype == JOIN_FULL)
	{
		fc_sjinfo->min_lefthand = bms_copy(fc_left_rels);
		fc_sjinfo->min_righthand = bms_copy(fc_right_rels);
		fc_sjinfo->lhs_strict = false; /* 不关心这个 */
		return fc_sjinfo;
	}

	/*
	 * 检索连接子句中提到的所有 relids。
	 */
	fc_clause_relids = pull_varnos(fc_root, (Node *) fc_clause);

	
/*
	 * 这个条件对于哪些 relids 是严格的，即如果 rel 的所有列都是 NULL，则无法成功？
	 */
	fc_strict_relids = find_nonnullable_rels((Node *) fc_clause);

	/* 记住该条件对于任何 LHS 关系是否严格 */
	fc_sjinfo->lhs_strict = bms_overlap(fc_strict_relids, fc_left_rels);

	/*
	 * 所需的 LHS 始终包括条件中提到的 LHS rels。根据较低的外连接，我们可能需要添加更多的 rel；见下文。
	 */
	fc_min_lefthand = bms_intersect(fc_clause_relids, fc_left_rels);

	/*
	 * 同样适用于所需的右侧。 但在这里，我们还必须包含任何较低
	 * 内连接，以确保我们不会尝试与其中任何一个交换。
	 */
	fc_min_righthand = bms_int_members(bms_union(fc_clause_relids, fc_inner_join_rels),
									fc_right_rels);

	/*
	 * 现在检查先前外连接的排序限制。
	 */
	foreach(fc_l, fc_root->join_info_list)
	{
		SpecialJoinInfo *fc_otherinfo = (SpecialJoinInfo *) lfirst(fc_l);

		/*
		 * 完全连接是一个优化 barrier：我们不能在它内部或外部关联。
		 * 因此，如果它与当前关系的左侧或右侧重叠，则扩展该侧的最小关系集以覆盖整个完全连接。
		 */
		if (fc_otherinfo->jointype == JOIN_FULL)
		{
			if (bms_overlap(fc_left_rels, fc_otherinfo->syn_lefthand) ||
				bms_overlap(fc_left_rels, fc_otherinfo->syn_righthand))
			{
				fc_min_lefthand = bms_add_members(fc_min_lefthand,
											   fc_otherinfo->syn_lefthand);
				fc_min_lefthand = bms_add_members(fc_min_lefthand,
											   fc_otherinfo->syn_righthand);
			}
			if (bms_overlap(fc_right_rels, fc_otherinfo->syn_lefthand) ||
				bms_overlap(fc_right_rels, fc_otherinfo->syn_righthand))
			{
				fc_min_righthand = bms_add_members(fc_min_righthand,
												fc_otherinfo->syn_lefthand);
				fc_min_righthand = bms_add_members(fc_min_righthand,
												fc_otherinfo->syn_righthand);
			}
			/* 对于完全连接无需做其他任何事情 */
			continue;
		}

		/*
		 * 对于我们左侧的较低外连接，如果我们的连接条件使用了较低
		 * 连接的右侧，并且对该关系没有严格性，我们必须保留
		 * 两个外连接的排序，因此将较低外连接的完整语法关系集添加到
		 * min_lefthand。 (我们必须使用它的完整语法关系集，而不仅仅是它的
		 * min_lefthand + min_righthand。 这是因为在这个外连接下面可能还有其他
		 * 外连接可以与这一个交换，但如果我们不与这个外连接交换，我们无法
		 * 与它们交换。) 另外，如果当前的连接是半连接或反连接，我们必须保留
		 * 排序，无论其是否严格。
		 *
		 * 注意：我相信我们必须坚持对较低外连接的min_righthand中的至少一个
		 * 关系严格，而不是它的整个syn_righthand。
		 */
		if (bms_overlap(fc_left_rels, fc_otherinfo->syn_righthand))
		{
			if (bms_overlap(fc_clause_relids, fc_otherinfo->syn_righthand) &&
				(fc_jointype == JOIN_SEMI || fc_jointype == JOIN_ANTI ||
				 !bms_overlap(fc_strict_relids, fc_otherinfo->min_righthand)))
			{
				fc_min_lefthand = bms_add_members(fc_min_lefthand,
											   fc_otherinfo->syn_lefthand);
				fc_min_lefthand = bms_add_members(fc_min_lefthand,
											   fc_otherinfo->syn_righthand);
			}
		}

		/*
		 * 对于我们右侧的较低外连接，如果我们的连接条件不使用
		 * 较低连接的右侧，并且较低外连接的连接条件是严格的，我们
		 * 可以交换两个外连接的排序；否则我们必须将较低外连接的完整
		 * 语法关系集添加到 min_righthand。
		 *
		 * 此外，如果我们的连接条件也不使用较低连接的左侧，强制
		 * 保留排序。否则我们可能会得到具有相同 min_righthands 的
		 * SpecialJoinInfos，这可能会使 join_is_legal 感到困惑（请参见
		 * backend/optimizer/README 中的讨论）。
		 *
		 * 此外，无论当前连接或较低外连接是半连接还是反连接，我们
		 * 必须保留排序。
		 *
		 * 在这里，我们必须考虑“我们的连接条件”包括任何
		 * 在较低外连接上述和我们连接以下的语法出现的子句；这些等同于
		 * 我们外连接中的退化子句，必须按此处理。显然，这些子句无法引用
		 * 我们的左侧，并且它们必须对较低外连接的右侧不严格（否则
		 * reduce_outer_joins 会将较低外连接简化为普通连接）。因此，
		 * 我们处理连接条件内的子句的其他方式不受它们的影响。因此，净
		 * 效果由 check_outerjoin_delay 为我们保存的 delay_upper_joins 标志
		 * 充分表示。
		 */
		if (bms_overlap(fc_right_rels, fc_otherinfo->syn_righthand))
		{
			if (bms_overlap(fc_clause_relids, fc_otherinfo->syn_righthand) ||
				!bms_overlap(fc_clause_relids, fc_otherinfo->min_lefthand) ||
				fc_jointype == JOIN_SEMI ||
				fc_jointype == JOIN_ANTI ||
				fc_otherinfo->jointype == JOIN_SEMI ||
				fc_otherinfo->jointype == JOIN_ANTI ||
				!fc_otherinfo->lhs_strict || fc_otherinfo->delay_upper_joins)
			{
				fc_min_righthand = bms_add_members(fc_min_righthand,
												fc_otherinfo->syn_lefthand);
				fc_min_righthand = bms_add_members(fc_min_righthand,
												fc_otherinfo->syn_righthand);
			}
		}
	}

	/*
	 * 检查 PlaceHolderVars。如果 PHV 应该在这个连接的可空边中进行评估，
	 * 那么确保 min_righthand 包含 PHV 的完整 eval_at 集。这确保了
	 * PHV 实际上可以在右侧进行评估。请注意，这仅在我们应该已经
	 * 确定了任何在此连接中语法上存在的 PHV 的最终 eval_at 级别时有效。
	 */
	foreach(fc_l, fc_root->placeholder_list)
	{
		PlaceHolderInfo *fc_phinfo = (PlaceHolderInfo *) lfirst(fc_l);
		Relids		fc_ph_syn_level = fc_phinfo->ph_var->phrels;

		/* 如果占位符没有从右侧语法上出现，则忽略 */
		if (!bms_is_subset(fc_ph_syn_level, fc_right_rels))
			continue;

		/* 否则，在我们评估 PHV 之前防止连接形成 */
		fc_min_righthand = bms_add_members(fc_min_righthand, fc_phinfo->ph_eval_at);
	}

	/*
	 * 如果我们没有找到可以放入 min_lefthand 的内容，则放弃并使
	 * 其成为完整的左侧，以避免有一个空的 min_lefthand，这将混淆后续
	 * 处理。 （我们不尝试对这样的情况聪明，只是纠正。）
	 * min_righthand 同样如此。
	 */
	if (bms_is_empty(fc_min_lefthand))
		fc_min_lefthand = bms_copy(fc_left_rels);
	if (bms_is_empty(fc_min_righthand))
		fc_min_righthand = bms_copy(fc_right_rels);

	/* 现在它们最好是非空的 */
	Assert(!bms_is_empty(fc_min_lefthand));
	Assert(!bms_is_empty(fc_min_righthand));
	/* 也不应重叠 */
	Assert(!bms_overlap(fc_min_lefthand, fc_min_righthand));

	fc_sjinfo->min_lefthand = fc_min_lefthand;
	fc_sjinfo->min_righthand = fc_min_righthand;

	return fc_sjinfo;
}

/*
 * compute_semijoin_info
 *	  填充新的 SpecialJoinInfo 的半连接相关字段
 *
 * 注意：这仅依赖于 SpecialJoinInfo 的 jointype 和 syn_righthand 字段；
 * 其他字段可能尚未设置。
 */
static void fc_compute_semijoin_info(PlannerInfo *fc_root, SpecialJoinInfo *fc_sjinfo, List *fc_clause)
{
	List	   *fc_semi_operators;
	List	   *fc_semi_rhs_exprs;
	bool		fc_all_btree;
	bool		fc_all_hash;
	ListCell   *fc_lc;

	/* 初始化半连接相关字段，以防我们无法唯一识别 */
	fc_sjinfo->semi_can_btree = false;
	fc_sjinfo->semi_can_hash = false;
	fc_sjinfo->semi_operators = NIL;
	fc_sjinfo->semi_rhs_exprs = NIL;

	/* 如果不是半连接，则无需做更多事情 */
	if (fc_sjinfo->jointype != JOIN_SEMI)
		return;

	
/*
	 * 查看半连接的连接条件是否由 AND 的
	 * 相等运算符组成，并且（仅）右侧变量位于每个
	 * 运算符的同一侧。如果是这样，我们可以确定如何强制右侧的唯一性。
	 *
	 * 注意，输入子句列表是与半连接
	 * *语法上* 关联的条件列表，这实际上意味着
	 * 为 IN 或 EXISTS 生成的比较列表。
	 * 特别是在后者的情况下，它可能包含与连接没有
	 * *语义上* 关联的子句，但仅涉及一侧或
	 * 另一侧。我们可以在这里忽略这些子句，因为它们将降到
	 * 处理其中一侧或另一侧。（在这里只考虑
	 * 语法关联的子句是可以的，因为对于半连接，
	 * 没有更高层次的条件可以引用右侧，因此
	 * 就不会有其他条件与此连接
	 * 在语义上关联。我们这样做是因为在提取
	 * 实际上与特定连接在语义上关联的条件列表之前，
	 * 拥有潜在的唯一化表达式集是有用的。）
	 *
	 * 注意 semi_operators 列表由连接条件运算符
	 * 本身组成（但如果需要，会交换以将右侧值放在右侧）。
	 * 这些可能是跨类型运算符，在这种情况下，实际上
	 * 需要的唯一性运算符是一个相关的单一类型运算符。
	 * 我们假设这个运算符在到达时将可以从 btree 或 hash
	 * 操作类中获得……如果没有，create_unique_plan() 将失败。
	 */
	fc_semi_operators = NIL;
	fc_semi_rhs_exprs = NIL;
	fc_all_btree = true;
	fc_all_hash = enable_hashagg;	/* 如果未启用，则不考虑哈希 */
	foreach(fc_lc, fc_clause)
	{
		OpExpr	   *fc_op = (OpExpr *) lfirst(fc_lc);
		Oid			fc_opno;
		Node	   *fc_left_expr;
		Node	   *fc_right_expr;
		Relids		fc_left_varnos;
		Relids		fc_right_varnos;
		Relids		fc_all_varnos;
		Oid			fc_opinputtype;

		/* 这是一个二元运算子子句吗？ */
		if (!IsA(fc_op, OpExpr) ||
			list_length(fc_op->args) != 2)
		{
			/* 不，但它是否引用了两侧？ */
			fc_all_varnos = pull_varnos(fc_root, (Node *) fc_op);
			if (!bms_overlap(fc_all_varnos, fc_sjinfo->syn_righthand) ||
				bms_is_subset(fc_all_varnos, fc_sjinfo->syn_righthand))
			{
				/*
				 * 子句仅引用一个关系，因此忽略它——除非它
				 * 包含易变函数，在这种情况下我们最好
				 * 放弃。
				 */
				if (contain_volatile_functions((Node *) fc_op))
					return;
				continue;
			}
			/* 引用两侧的非运算符子句，必须放弃 */
			return;
		}

		/* 从二元运算子子句中提取数据 */
		fc_opno = fc_op->opno;
		fc_left_expr = linitial(fc_op->args);
		fc_right_expr = lsecond(fc_op->args);
		fc_left_varnos = pull_varnos(fc_root, fc_left_expr);
		fc_right_varnos = pull_varnos(fc_root, fc_right_expr);
		fc_all_varnos = bms_union(fc_left_varnos, fc_right_varnos);
		fc_opinputtype = exprType(fc_left_expr);

		/* 它是否引用了两侧？ */
		if (!bms_overlap(fc_all_varnos, fc_sjinfo->syn_righthand) ||
			bms_is_subset(fc_all_varnos, fc_sjinfo->syn_righthand))
		{
			/*
			 * 子句仅引用一个关系，因此忽略它——除非它
			 * 包含易变函数，在这种情况下我们最好放弃。
			 */
			if (contain_volatile_functions((Node *) fc_op))
				return;
			continue;
		}

		/* 检查参数的关系成员资格 */
		if (!bms_is_empty(fc_right_varnos) &&
			bms_is_subset(fc_right_varnos, fc_sjinfo->syn_righthand) &&
			!bms_overlap(fc_left_varnos, fc_sjinfo->syn_righthand))
		{
			/* 典型情况，right_expr 是右侧变量 */
		}
		else if (!bms_is_empty(fc_left_varnos) &&
				 bms_is_subset(fc_left_varnos, fc_sjinfo->syn_righthand) &&
				 !bms_overlap(fc_right_varnos, fc_sjinfo->syn_righthand))
		{
			/* 翻转情况，left_expr 是右侧变量 */
			fc_opno = get_commutator(fc_opno);
			if (!OidIsValid(fc_opno))
				return;
			fc_right_expr = fc_left_expr;
		}
		else
		{
			/* 参数的混合成员资格，放弃处理 */
			return;
		}

		/* 所有操作符必须是 btree 相等或 hash 相等 */
		if (fc_all_btree)
		{
			/* oprcanmerge 被视为提示... */
			if (!op_mergejoinable(fc_opno, fc_opinputtype) ||
				get_mergejoin_opfamilies(fc_opno) == NIL)
				fc_all_btree = false;
		}
		if (fc_all_hash)
		{
			/* ...但 oprcanhash 最好是正确的 */
			if (!op_hashjoinable(fc_opno, fc_opinputtype))
				fc_all_hash = false;
		}
		if (!(fc_all_btree || fc_all_hash))
			return;

		/* 到目前为止，一切正常，继续构建列表 */
		fc_semi_operators = lappend_oid(fc_semi_operators, fc_opno);
		fc_semi_rhs_exprs = lappend(fc_semi_rhs_exprs, copyObject(fc_right_expr));
	}

	/* 如果我们没有找到至少一个列来唯一化，则放弃处理 */
	if (fc_semi_rhs_exprs == NIL)
		return;

	/*
	 * 我们需要唯一化的表达式不能是易失性的。
	 */
	if (contain_volatile_functions((Node *) fc_semi_rhs_exprs))
		return;

	/*
	 * 如果我们来到这里，我们可以使用至少一种排序或哈希
	 * 来唯一化半连接的右侧。保存如何做到这一点的信息。
	 */
	fc_sjinfo->semi_can_btree = fc_all_btree;
	fc_sjinfo->semi_can_hash = fc_all_hash;
	fc_sjinfo->semi_operators = fc_semi_operators;
	fc_sjinfo->semi_rhs_exprs = fc_semi_rhs_exprs;
}


/*****************************************************************************
 *
 *	  资格
 *
 *****************************************************************************/

/*
 * distribute_qual_to_rels
 *	  将子句信息添加到每个在子句中提到的基关系的 baserestrictinfo 或 joininfo 列表中
 *	  （具体取决于子句是否为连接）。为每个关系创建并添加一个 RestrictInfo 节点到
 *	  适当的列表中。或者，如果子句使用可合并的操作符并且没有被外连接规则延迟，
 *	  将左右侧表达式输入到查询的 EquivalenceClasses 列表中。或者，如果子句需要被视为
 *	  属于更高的连接级别，则仅将其添加到 postponed_qual_list 中。
 *
 * 'clause'：要分发的资格子句
 * 'below_outer_join'：如果资格来自高层外连接的可空侧的 JOIN/ON，则为 true
 * 'jointype'：资格来自的连接类型（对于 WHERE 子句为 JOIN_INNER）
 * 'security_level'：分配给资格的安全级别
 * 'qualscope'：资格的语法范围所覆盖的基关系集合
 * 'ojscope'：如果不是外连接资格则为 NULL，否则形成该连接所需的最小基关系集合
 * 'outerjoin_nonnullable'：如果不是外连接资格则为 NULL，否则出现在连接的外侧（非空侧）的基关系集合
 *		（对于 FULL JOIN，这包括连接的两个侧，实际上必须等于 qualscope）
 * 'postponed_qual_list'：PostponedQual 结构的列表，如果该资格属于更高连接级别，我们可以将其添加到此列表中。
 *		如果调用者知道延期是不可能的，则可以为 NULL。
 *
 * 'qualscope' 标识资格来自的连接级别的语法。 
 * 'ojscope' 在我们决定将资格强制提升到外连接级别时需要, 这个将是 ojscope，不一定是 qualscope。
 *
 * 在调用此时，root->join_info_list 必须包含所有仅在该资格下语法的特殊连接的条目。
 */
static void fc_distribute_qual_to_rels(PlannerInfo *fc_root, Node *fc_clause,
						bool fc_below_outer_join,
						JoinType fc_jointype,
						Index fc_security_level,
						Relids fc_qualscope,
						Relids fc_ojscope,
						Relids fc_outerjoin_nonnullable,
						List **fc_postponed_qual_list)
{
	Relids		fc_relids;
	bool		fc_is_pushed_down;
	bool		fc_outerjoin_delayed;
	bool		fc_pseudoconstant = false;
	bool		fc_maybe_equivalence;
	bool		fc_maybe_outer_join;
	Relids		fc_nullable_relids;
	RestrictInfo *fc_restrictinfo;

	/*
	 * 检索子句中提到的所有关系 ID。
	 */
	fc_relids = pull_varnos(fc_root, fc_clause);

	/*
	 * 在普通 SQL 中，WHERE 或 JOIN/ON 子句不能引用任何不在其语法范围内的关系；
	 * 然而，如果我们拉起了一个 LATERAL 子查询，则可能在已拉起的资格中发现此类引用。
	 * 我们需要将这种资格视为属于包含它们引用的每个关系的连接级别。
	 * 虽然我们可以让 pull_up_subqueries() 最初将这些资格放置正确，但在这里处理会更简单。
	 * 当我们发现包含超出其语法范围的变量的子句时，我们将其添加到延期资格列表，并且
	 * 在我们递归返回到适当的连接级别后进行处理。
	 */
	if (!bms_is_subset(fc_relids, fc_qualscope))
	{
		PostponedQual *fc_pq = (PostponedQual *) palloc(sizeof(PostponedQual));

		Assert(fc_root->hasLateralRTEs);	/* 否则不应该发生 */
		Assert(fc_jointype == JOIN_INNER); /* 不能推迟到外连接之后 */
		fc_pq->qual = fc_clause;
		fc_pq->relids = fc_relids;
		*fc_postponed_qual_list = lappend(*fc_postponed_qual_list, fc_pq);
		return;
	}

	/*
	 * 如果是外连接子句，还要检查 relids 是否是
	 * ojscope 的子集。（如果语法范围检查通过，这不应该失败。）
	 */
	if (fc_ojscope && !bms_is_subset(fc_relids, fc_ojscope))
		elog(ERROR, "JOIN qualification cannot refer to other relations");

	/*
	 * 如果子句是无变量的，我们平常的向下推送到提到的关系
	 * 的启发式方法不起作用，因为没有关系。
	 *
	 * 如果该子句是外连接子句，我们必须强制它到 OJ 的
	 * 语义级别以保持语义。
	 *
	 * 否则，当子句包含波动函数时，我们强制它在其原始语法
	 * 级别进行评估。这保持了预期的语义。
	 *
	 * 当子句中也没有波动函数时，它实际上是一个
	 * 在计划的单次执行过程中不会改变值的伪常量子句，因此可以在
	 * 一次性条件中用作 gating Result 计划节点。我们暂时将这样的子句放入常规
	 * RestrictInfo 列表中，但最终 createplan.c 会将其提取并立即在
	 * 分配给伪常量子句的任何计划节点之上制作一个 gating Result 节点。通常最好将 gating 节点尽可能高地放置在计划树中。如果我们不在外连接下，我们实际上可以将伪常量条件推到树的最上面。如果我们在外连接下，我们则将条件留在其原始语法
	 * 级别（我们可以将其推送到外连接之下，但这似乎比其值得更复杂）。
	 */
	if (bms_is_empty(fc_relids))
	{
		if (fc_ojscope)
		{
			
/* 子句附加到外连接上，在那里评估它 */
			fc_relids = bms_copy(fc_ojscope);
			/* 不能用作限制条件，因此不要标记为伪常量 */
		}
		else
		{
			/* 在原始句法级别评估 */
			fc_relids = bms_copy(fc_qualscope);
			if (!contain_volatile_functions(fc_clause))
			{
				/* 标记为限制条件 */
				fc_pseudoconstant = true;
				/* 告诉 createplan.c 检查限制条件 */
				fc_root->hasPseudoConstantQuals = true;
				/* 如果不在外连接下方，将其推送到树的顶部 */
				if (!fc_below_outer_join)
				{
					fc_relids =
						get_relids_in_jointree((Node *) fc_root->parse->jointree,
											   false);
					fc_qualscope = bms_copy(fc_relids);
				}
			}
		}
	}

	/*----------
	 * 检查子句应用是否必须因外连接的考虑而延迟。
	 *
	 * 关于 is_pushed_down 的说明：如果子句在与其原始句法级别不同的级别上（潜在地）适用，我们将其标记为“下推”。
	 * 此标志用于区分 OUTER JOIN ON 约束与下推到同一连接关系的其他约束。规则是：
	 *		WHERE 约束和 INNER JOIN 约束：is_pushed_down = true。
	 *		非退化的 OUTER JOIN 约束：is_pushed_down = false。
	 *		退化的 OUTER JOIN 约束：is_pushed_down = true。
	 * “退化”OUTER JOIN 约束是不提及非空侧的约束，因此可以被下推到可为空侧，而不会改变连接结果。
	 * 在其评估的级别上，将其视为常规过滤条件是正确的。
	 *
	 * 注意：并不立即显而易见的是，简单的布尔值不足以满足此要求：
	 * 如果由于某种原因我们将退化的约束附加到其原始连接级别，
	 * 它在那里的作用需要被视为外连接约束。然而，这种情况不会发生，
	 * 因为所有子句提到的关系必须在外连接的 min_righthand 中，因此
	 * 所需的连接必须在外连接之前形成；我们始终将约束附加到可以评估的最低级别。
	 * 但是如果我们曾经重新引入延迟评估“昂贵”约束的机制，这个领域将需要工作。
	 *
	 * 注意：一般来说，使用 is_pushed_down 必须通过宏 RINFO_IS_PUSHED_DOWN 进行，
	 * 因为仅该标志并不总是足以判断子句在上下文中是否必须作为下推处理。
	 * 这似乎是另一个需要重新思考的理由。
	 *----------
	 */
	if (bms_overlap(fc_relids, fc_outerjoin_nonnullable))
	{
		/*
		 * 该约束附加到外连接并提及（一些）非空侧的关系，因此它不是退化的。
		 *
		 * 我们不能使用这样的子句来推断等价性（左侧和右侧可能在连接上方不相等，因为其中一个可能为 NULL）...
		 * 但如果它是可合并连接的，我们可能能够用它进行更有限的推导。
		 * 因此考虑将其添加到保留的外连接子句列表中。
		 */
		fc_is_pushed_down = false;
		fc_maybe_equivalence = false;
		fc_maybe_outer_join = true;

		/* 检查是否必须因下方的外连接而延迟 */
		fc_outerjoin_delayed = fc_check_outerjoin_delay(fc_root,
												  &fc_relids,
												  &fc_nullable_relids,
												  false);

		/*
		 * 现在强制约束在与外连接相对应的连接级别上进行评估。
		 * 我们不能让它被推送到非空侧，因为这样我们将不会产生输出行，
		 * 而是对于任何未满足该约束的非空侧行，产生预期的单个空扩展行。
		 *
		 * （在调用 check_outerjoin_delay 之后执行此步骤，因为它会破坏 relids。）
		 */
		Assert(fc_ojscope);
		fc_relids = fc_ojscope;
		Assert(!fc_pseudoconstant);
	}
	else
	{
		/*
		 * 正常约束子句或退化的外连接子句。无论哪种情况，
		 * 我们都可以将其标记为下推。
		 */
		fc_is_pushed_down = true;

		/* 检查是否必须因下方的外连接而延迟 */
		fc_outerjoin_delayed = fc_check_outerjoin_delay(fc_root,
												  &fc_relids,
												  &fc_nullable_relids,
												  true);

		if (fc_outerjoin_delayed)
		{
			/* 应该仍然是当前范围的一个子集... */
			Assert(fc_root->hasLateralRTEs || bms_is_subset(fc_relids, fc_qualscope));
			Assert(fc_ojscope == NULL || bms_is_subset(fc_relids, fc_ojscope));

			/*
			 * 因为定性条件的应用将被外连接延迟，
			 * 我们不能假设它的变量在所有地方都是相等的。
			 */
			fc_maybe_equivalence = false;

			/*
			 * 这可能是一个与下方反连接冗余的 IS NULL 条款；
			 * 如果是这样我们可以直接丢弃它。
			 * 我们在其他情况下不需要测试，因为这只会
			 * 在下推、延迟的条款中可能发生。
			 */
			if (fc_check_redundant_nullability_qual(fc_root, fc_clause))
				return;
		}
		else
		{
			/*
			 * 定性条件不受任何下方外连接限制的延迟影响，
			 * 因此我们可以考虑将其提供给
			 * 等价性机制。然而，如果它本身在一个外连接条款中，
			 * 则将其视为好像它出现在该外连接下方（请注意，
			 * 只有在该条款仅引用可空侧关系时才会到达这里）。
			 */
			fc_maybe_equivalence = true;
			if (fc_outerjoin_nonnullable != NULL)
				fc_below_outer_join = true;
		}

		/*
		 * 由于它未提及左侧，因此即使它在外连接中，
		 * 作为一个被置留的外连接条款也肯定没有用处。
		 */
		fc_maybe_outer_join = false;
	}

	/*
	 * 构建 RestrictInfo 节点本身。
	 */
	fc_restrictinfo = make_restrictinfo(fc_root,
									 (Expr *) fc_clause,
									 fc_is_pushed_down,
									 fc_outerjoin_delayed,
									 fc_pseudoconstant,
									 fc_security_level,
									 fc_relids,
									 fc_outerjoin_nonnullable,
									 fc_nullable_relids);

	/*
	 * 如果它是一个连接条款（无论是自然的，
	 * 还是由于外连接规则的延迟），将条款中使用的变量
	 * 添加到它们关系的目标列表中，以便扫描
	 * 这些关系的计划节点会发出它们（否则它们在连接节点上不可用！）。
	 *
	 * 注意：如果该条款被吸收入 EquivalenceClass，
	 * 那么这可能是不必要的，但是目前我们必须这样做，
	 * 以涵盖 EC 变为 ec_broken 的情况，最终将
	 * 原始条款重新插入到计划中。
	 */
	if (bms_membership(fc_relids) == BMS_MULTIPLE)
	{
		List	   *fc_vars = pull_var_clause(fc_clause,
										   PVC_RECURSE_AGGREGATES |
										   PVC_RECURSE_WINDOWFUNCS |
										   PVC_INCLUDE_PLACEHOLDERS);

		add_vars_to_targetlist(fc_root, fc_vars, fc_relids, false);
		list_free(fc_vars);
	}

	/*
	 * 我们检查每个条款的“可合并性”，而不仅仅是连接条款，
	 * 因为我们希望了解同一关系的变量之间，
	 * 或变量与常量之间的等价性。
	 */
	fc_check_mergejoinable(fc_restrictinfo);

	/*
	 * 如果这是真正的等价条款，则将其发送到 EquivalenceClass
	 * 机制。我们 *不* 直接将其附加到任何限制或连接
	 * 列表中。EC 代码稍后会将其传播到适当的位置。
	 *
	 * 如果该条款具有可合并的运算符且不是
	 * 外连接延迟，但由于它是一个外连接条款而不是等价的，
	 * EC 代码可能仍然能够对此做些事情。
	 * 我们将其添加到适当的列表以便稍后进一步考虑。
	 * 具体而言：
	 *
	 * 如果它是一个与外连接的两个侧面相关的左或右外连接
	 * 资格（没有像 leftvar1 = leftvar2 + rightvar
	 * 之类的奇怪事），我们将其添加到
	 * root->left_join_clauses 或
	 * root->right_join_clauses 中，具体取决于非空变量出现在
	 * 哪一侧。
	 *
	 * 如果它是一个完全外连接的资格，我们将其添加到
	 * root->full_join_clauses。（理想情况下，我们会丢弃
	 * 不符合 leftvar = rightvar 的情况，正如我们对左/右连接所做的，
	 * 但是这个例程没有所需的信息来做到这一点；
	 * 而当前对 full_join_clauses 列表的用途也不要求这样，
	 * 因此目前不值得复杂化这个例程的 API 来实现这一点。）
	 *
	 * 如果上述都不成立，则将其交给
	 * distribute_restrictinfo_to_rels()。
	 *
	 * 在所有情况下，初始化可合并条款的 left_ec 和 right_ec
	 * 字段非常重要，以便所有可能的可合并表达式在
	 * EquivalenceClasses 中都有表示。如果
	 * process_equivalence 成功，它将处理此事；
	 * 否则，我们必须调用 initialize_mergeclause_eclasses 来完成此操作。
	 */
	if (fc_restrictinfo->mergeopfamilies)
	{
		if (fc_maybe_equivalence)
		{
			if (fc_check_equivalence_delay(fc_root, fc_restrictinfo) &&
				process_equivalence(fc_root, &fc_restrictinfo, fc_below_outer_join))
				return;
			/* EC 拒绝了它，所以以艰难的方式设置 left_ec/right_ec... */
			if (fc_restrictinfo->mergeopfamilies)	/* EC 可能已更改此内容 */
				initialize_mergeclause_eclasses(fc_root, fc_restrictinfo);
			/* ... 并向下掉入 distribute_restrictinfo_to_rels */
		}
		else if (fc_maybe_outer_join && fc_restrictinfo->can_join)
		{
			/* 我们需要以艰难的方式设置 left_ec/right_ec */
			initialize_mergeclause_eclasses(fc_root, fc_restrictinfo);
			/* 现在看看它是否应该进入任何外连接列表 */
			if (bms_is_subset(fc_restrictinfo->left_relids,
							  fc_outerjoin_nonnullable) &&
				!bms_overlap(fc_restrictinfo->right_relids,
							 fc_outerjoin_nonnullable))
			{
				/* 我们有 outervar = innervar */
				fc_root->left_join_clauses = lappend(fc_root->left_join_clauses,
												  fc_restrictinfo);
				return;
			}
			if (bms_is_subset(fc_restrictinfo->right_relids,
							  fc_outerjoin_nonnullable) &&
				!bms_overlap(fc_restrictinfo->left_relids,
							 fc_outerjoin_nonnullable))
			{
				/* 我们有 innervar = outervar */
				fc_root->right_join_clauses = lappend(fc_root->right_join_clauses,
												   fc_restrictinfo);
				return;
			}
			if (fc_jointype == JOIN_FULL)
			{
				/* 完全连接（在这种情况下上述测试无法匹配） */
				fc_root->full_join_clauses = lappend(fc_root->full_join_clauses,
												  fc_restrictinfo);
				return;
			}
			/* 不，所以下降到 distribute_restrictinfo_to_rels */
		}
		else
		{
			/* 我们仍然需要设置 left_ec/right_ec */
			initialize_mergeclause_eclasses(fc_root, fc_restrictinfo);
		}
	}

	/* 没有适用的 EC 特殊情况，因此将其推入条款列表中 */
	distribute_restrictinfo_to_rels(fc_root, fc_restrictinfo);
}


/*
 * check_outerjoin_delay
 *		检测是否由于存在下层外连接，给定 relids 的引用条件必须在应用中延迟，
 *		并/或可能会强制更高层外连接的额外延迟。
 *
 * 如果条件必须延迟，请将 relids 添加到 *relids_p 中，以反映评估该条件的最低安全级别，并返回 true。 
 * 对更高层外连接的任何额外延迟通过在 SpecialJoinInfo 结构中将 delay_upper_joins 设置为 true 来反映。 
 * 我们还计算 nullable_relids，即由下层外连接 nullable 的引用 relids 集（注意，即使对于非延迟条件，这也可能是非空的）。
 *
 * 对于一个被下推的条件，我们可以在 (1) 获得它提到的所有表时，以及 (2) 我们处于或高于任何可以将这些表置为 null 的外连接并且在给定条件的句法位置之下时，立即评估该条件。 
 * 我们必须强制执行 (2)，因为将这样的子句下推到 OJ 之下可能导致 OJ 发出不该形成的 null 扩展行，或者这些行应该被该子句拒绝。 
 * （这只是非严格条件的问题，因为如果我们能证明一个仅提到 nullable 表的条件是严格的，我们将在 reduce_outer_joins() 中将外连接减少为内连接。）
 *
 * 为了强制执行 (2)，扫描 join_info_list 并将任何这样的 OJ 的所需 relid 集合合并到子句自己的引用列表中。 
 * 在我们被调用时，join_info_list 仅包含该条件下的外连接。 
 * 我们必须重复扫描，直到没有新的 relids 被添加；这确保无论 OJ 处理的顺序如何，该条件都被适当地延迟。 
 * 作为一个例子，如果我们有一个 OJ 具有 LHS=A，RHS=B，以及一个具有 LHS=B，RHS=C 的 OJ，隐含这些可以以任一顺序进行；如果 B/C 连接先完成，则连接到 A 可以将 C 置为 null，因此实际上仅提到 C 的条件不能在连接到 A 之下应用。
 *
 * 对于一个未下推的条件，这不会确定我们放置条件的位置，但我们需要确定 outerjoin_delayed 和 nullable_relids，以便在后续的规划过程中使用。
 *
 * 最后，一个参考了任何当前 join_info_list 成员的 nullable 侧的被下推条件，并且必须在该 OJ 之上评估（因为它的所需 relids 也重叠 LHS）会导致该 OJ 的 delay_upper_joins 标志被设置为 true。 
 * 这将阻止任何更高层 OJ 与该 OJ 互换，这将导致没有正确的位置来评估该条件。 
 * （我们关心的情况是一些外连接的 RHS 中的子选择 WHERE 子句。 
 * 该 WHERE 子句必须有效地被视为该外连接条件的一个退化子句。 
 * 与其直接尝试通过连接来匹配这样的子句，不如在这里设置 delay_upper_joins，并且当上层外连接通过 make_outerjoininfo 处理时，它将避免允许这两个 OJ 交换位置。）
 */
static bool fc_check_outerjoin_delay(PlannerInfo *fc_root,
					  Relids *fc_relids_p, /* 输入/输出参数 */
					  Relids *fc_nullable_relids_p,	/* 输出参数 */
					  bool fc_is_pushed_down)
{
	Relids		fc_relids;
	Relids		fc_nullable_relids;
	bool		fc_outerjoin_delayed;
	bool		fc_found_some;

	/* 如果没有特殊连接则为快速路径 */
	if (fc_root->join_info_list == NIL)
	{
		*fc_nullable_relids_p = NULL;
		return false;
	}

	/* 必须复制 relids，因为我们需要在最后保留原始值 */
	fc_relids = bms_copy(*fc_relids_p);
	fc_nullable_relids = NULL;
	fc_outerjoin_delayed = false;
	do
	{
		ListCell   *fc_l;

		fc_found_some = false;
		foreach(fc_l, fc_root->join_info_list)
		{
			SpecialJoinInfo *fc_sjinfo = (SpecialJoinInfo *) lfirst(fc_l);

			/* 我们是否引用了这个 OJ 的任何可空 rel？ */
			if (bms_overlap(fc_relids, fc_sjinfo->min_righthand) ||
				(fc_sjinfo->jointype == JOIN_FULL &&
				 bms_overlap(fc_relids, fc_sjinfo->min_lefthand)))
			{
				/* 是的；我们是否已将所有的 rel 包括在 relids 中？ */
				if (!bms_is_subset(fc_sjinfo->min_lefthand, fc_relids) ||
					!bms_is_subset(fc_sjinfo->min_righthand, fc_relids))
				{
					/* 没有，所以添加它们 */
					fc_relids = bms_add_members(fc_relids, fc_sjinfo->min_lefthand);
					fc_relids = bms_add_members(fc_relids, fc_sjinfo->min_righthand);
					fc_outerjoin_delayed = true;
					/* 我们需要再进行一次迭代 */
					fc_found_some = true;
				}
				/* 跟踪所有相关 OJs 的可空 rel */
				fc_nullable_relids = bms_add_members(fc_nullable_relids,
												  fc_sjinfo->min_righthand);
				if (fc_sjinfo->jointype == JOIN_FULL)
					fc_nullable_relids = bms_add_members(fc_nullable_relids,
													  fc_sjinfo->min_lefthand);
				/* 如有必要，设置 delay_upper_joins */
				if (fc_is_pushed_down && fc_sjinfo->jointype != JOIN_FULL &&
					bms_overlap(fc_relids, fc_sjinfo->min_lefthand))
					fc_sjinfo->delay_upper_joins = true;
			}
		}
	} while (fc_found_some);

	/* 仅识别实际引用的可空 rel */
	fc_nullable_relids = bms_int_members(fc_nullable_relids, *fc_relids_p);

	/* 替换 *relids_p，并返回 nullable_relids */
	bms_free(*fc_relids_p);
	*fc_relids_p = fc_relids;
	*fc_nullable_relids_p = fc_nullable_relids;
	return fc_outerjoin_delayed;
}

/*
 * check_equivalence_delay
 *		检测潜在的等价子句是否由于外连接延迟而变得不安全。
 *		如果安全则返回 true。
 *
 * distribute_qual_to_rels 中的初步测试会将可以合并连接的
 * 子句视为潜在的等价子句，如果它不是外连接延迟的。
 * 但因为等价处理的目的是我们将与其他子句重新组合
 * 该子句的两个侧面，我们必须检查每个侧面
 * 单独满足非外连接延迟的条件；否则在我们可以放置派生的
 * 等价条件的地方进行求值可能并不安全。
 */
static bool fc_check_equivalence_delay(PlannerInfo *fc_root,
						RestrictInfo *fc_restrictinfo)
{
	Relids		fc_relids;
	Relids		fc_nullable_relids;

	/* 如果没有特殊连接则为快速路径 */
	if (fc_root->join_info_list == NIL)
		return true;

	/* 必须复制 restrictinfo 的 relids 以避免改变它 */
	fc_relids = bms_copy(fc_restrictinfo->left_relids);
	/* 检查左侧是否不需要延迟 */
	if (fc_check_outerjoin_delay(fc_root, &fc_relids, &fc_nullable_relids, true))
		return false;

	/* 右侧也是如此 */
	fc_relids = bms_copy(fc_restrictinfo->right_relids);
	if (fc_check_outerjoin_delay(fc_root, &fc_relids, &fc_nullable_relids, true))
		return false;

	return true;
}

/*
 * check_redundant_nullability_qual
 *	  检查该 qual 是否为与下层 JOIN_ANTI 连接冗余的 IS NULL qual。
 *
 * 我们想要抑制冗余的 IS NULL quals，不是为了节省周期，
 * 而是为了避免为它们生成虚假的选择性估计。因此，如果在这里
 * 检测到冗余，distribute_qual_to_rels() 就会丢弃该 qual。
 */
static bool fc_check_redundant_nullability_qual(PlannerInfo *fc_root, Node *fc_clause)
{
	Var		   *fc_forced_null_var;
	Index		fc_forced_null_rel;
	ListCell   *fc_lc;

	/* 检查 IS NULL，并识别被强制为 NULL 的 Var */
	fc_forced_null_var = find_forced_null_var(fc_clause);
	if (fc_forced_null_var == NULL)
		return false;
	fc_forced_null_rel = fc_forced_null_var->varno;

	/*
	 * 如果该 Var 来自下层反连接的可空侧，IS
	 * NULL 条件必然为真。
	 */
	foreach(fc_lc, fc_root->join_info_list)
	{
		SpecialJoinInfo *fc_sjinfo = (SpecialJoinInfo *) lfirst(fc_lc);

		if (fc_sjinfo->jointype == JOIN_ANTI &&
			bms_is_member(fc_forced_null_rel, fc_sjinfo->syn_righthand))
			return true;
	}

	return false;
}

/*
 * distribute_restrictinfo_to_rels
 *	  将完成的 RestrictInfo 推送到适当的限制或连接
 *	  子句列表。
 *
 * 这是 distribute_qual_to_rels() 对普通 qual
 * 子句的最后一步。对等价类处理有兴趣的子句会转到
 * EC 机制，但最终可能会再次反馈到这里。
 */
void distribute_restrictinfo_to_rels(PlannerInfo *fc_root,
								RestrictInfo *fc_restrictinfo)
{
	Relids		fc_relids = fc_restrictinfo->required_relids;
	RelOptInfo *fc_rel;

	switch (bms_membership(fc_relids))
	{
		case BMS_SINGLETON:

			/*
			 * 子句中仅参与一个关系，因此它
			 * 是该关系的限制子句。
			 */
			fc_rel = find_base_rel(fc_root, bms_singleton_member(fc_relids));

			/* 将子句添加到 rel 的限制列表中 */
			fc_rel->baserestrictinfo = lappend(fc_rel->baserestrictinfo,
											fc_restrictinfo);
			/* 更新安全级别信息 */
			fc_rel->baserestrict_min_security = Min(fc_rel->baserestrict_min_security,
												 fc_restrictinfo->security_level);
			break;
		case BMS_MULTIPLE:

			/*
			 * 该子句是一个连接子句，因为在其 relid 集
			 * 中有多个 rel。
			 */

			/*
			 * 检查是否有可哈希连接的操作符。 （我们只在真正的连接子句中设置
			 * 哈希连接信息。）
			 */
			fc_check_hashjoinable(fc_restrictinfo);

			/*
			 * 同样，检查该子句是否适合与 Memoize 节点一起使用，以在参数化
			 * 嵌套循环中缓存内部元组。
			 */
			fc_check_memoizable(fc_restrictinfo);

			/*
			 * 将子句添加到所有相关关系的连接列表中。
			 */
			add_join_clause_to_rels(fc_root, fc_restrictinfo, fc_relids);
			break;
		default:

			/*
			 * 子句未引用任何 rel，因此我们无处
			 * 附加它。如果调用者正常工作，不应该到达这里。
			 */
			elog(ERROR, "cannot cope with variable-free clause");
			break;
	}
}

/*
 * process_implied_equality
 *	  创建一个 restrictinfo 项，表示 "item1 op item2"，并将其推送
 *	  到适当的列表中。 （实际上 opno 总是一个 btree
 *	  等价操作符。）
 *
 * "qualscope" 是赋予 restrictinfo 的名义语法级别。
 * 这必须至少包含表达式中使用的所有 rel，但它
 * 仅用于在两个 expr 都没有变量时设置 qual 应用级别。
 * 否则，qual 在提供其所有变量的最低连接级别上应用。
 *
 * "nullable_relids" 是在表达式中使用的 relids 集，这些 relids
 * 在表达式下方可能可空。 （这必须由调用者提供，因为该函数
 * 在解构 jointree 后使用，因此我们不知道子句项来自哪里。）
 *
 * "security_level" 是分配给新 restrictinfo 的安全级别。
 *
 * "both_const" 表示两个项是否都是已知的伪常量；
 * 在这种情况下，值得应用 eval_const_expressions()，以防
 * 我们能够生成常量 TRUE 或常量 FALSE。 （否则就不值得，
 * 因为表达式已经通过 eval_const_expressions。）
 *
 * 返回生成的 RestrictInfo（如果有）。如果 both_const 为真并且我们成功将
 * 子句简化为常量 TRUE，则结果将为 NULL。
 *
 * 注意：该函数将复制 item1 和 item2，但确保 Relids 参数是新副本
 * 而不是共享其他用途是调用者的责任。
 *
 * 注意：我们不在此处调用 initialize_mergeclause_eclasses()。
 * 确保 left_ec/right_ec 根据需要设置是调用者的责任。
 */
RestrictInfo * process_implied_equality(PlannerInfo *fc_root,
						 Oid fc_opno,
						 Oid fc_collation,
						 Expr *fc_item1,
						 Expr *fc_item2,
						 Relids fc_qualscope,
						 Relids fc_nullable_relids,
						 Index fc_security_level,
						 bool fc_below_outer_join,
						 bool fc_both_const)
{
	RestrictInfo *fc_restrictinfo;
	Node	   *fc_clause;
	Relids		fc_relids;
	bool		fc_pseudoconstant = false;

	
/*
	 * 构建新子句。复制以确保其与原始子句没有共享的子结构（这是必要的，以防其中有子查询...）
	 */
	fc_clause = (Node *) make_opclause(fc_opno,
									BOOLOID,	/* opresulttype */
									false,	/* opretset */
									copyObject(fc_item1),
									copyObject(fc_item2),
									InvalidOid,
									fc_collation);

	/* 如果两个都是常量，尝试简化为布尔常量。 */
	if (fc_both_const)
	{
		fc_clause = eval_const_expressions(fc_root, fc_clause);

		/* 如果我们生成了常量 TRUE，则只需删除该子句 */
		if (fc_clause && IsA(fc_clause, Const))
		{
			Const	   *fc_cclause = (Const *) fc_clause;

			Assert(fc_cclause->consttype == BOOLOID);
			if (!fc_cclause->constisnull && DatumGetBool(fc_cclause->constvalue))
				return NULL;
		}
	}

	/*
	 * 剩下的部分是 distribute_qual_to_rels 的一个简化版本。
	 * 我们可以跳过其中的大部分工作，但仍有几个特殊案例需要处理。
	 *
	 * 检索在可能简化的子句中提到的所有 relids。
	 */
	fc_relids = pull_varnos(fc_root, fc_clause);
	Assert(bms_is_subset(fc_relids, fc_qualscope));

	/*
	 * 如果子句没有变量，我们正常的将其推送到提到的 rels 的启发式方法就不适用了，因为没有 rel。
	 * 在给定的 qualscope 下应用，或者在树的顶端应用，如果它是非挥发的（它很可能是非挥发的，
	 * 但我们会检查以确保）。
	 */
	if (bms_is_empty(fc_relids))
	{
		/* 在原始句法级别评估 */
		fc_relids = bms_copy(fc_qualscope);
		if (!contain_volatile_functions(fc_clause))
		{
			/* 标记为限制条件 */
			fc_pseudoconstant = true;
			/* 告诉 createplan.c 检查限制条件 */
			fc_root->hasPseudoConstantQuals = true;
			/* 如果不在外连接下方，将其推送到树的顶部 */
			if (!fc_below_outer_join)
			{
				fc_relids =
					get_relids_in_jointree((Node *) fc_root->parse->jointree,
										   false);
			}
		}
	}

	/*
	 * 构建 RestrictInfo 节点本身。
	 */
	fc_restrictinfo = make_restrictinfo(fc_root,
									 (Expr *) fc_clause,
									 true,	/* is_pushed_down */
									 false, /* outerjoin_delayed */
									 fc_pseudoconstant,
									 fc_security_level,
									 fc_relids,
									 NULL,	/* outer_relids */
									 fc_nullable_relids);

	/*
	 * 如果这是一个连接子句，将子句中使用的变量添加到其关系的目标列表中，
	 * 以便由扫描这些关系的计划节点发出（否则它们在连接节点不可用！）。
	 *
	 * 通常，在 distribute_qual_to_rels 首次看到组件表达式时，我们会已经完成此操作；
	 * 但有可能有一些变量因为最初只出现在单关系子句中而未曾这样做。所以这里为了安全起见再做一次。
	 */
	if (bms_membership(fc_relids) == BMS_MULTIPLE)
	{
		List	   *fc_vars = pull_var_clause(fc_clause,
										   PVC_RECURSE_AGGREGATES |
										   PVC_RECURSE_WINDOWFUNCS |
										   PVC_INCLUDE_PLACEHOLDERS);

		add_vars_to_targetlist(fc_root, fc_vars, fc_relids, false);
		list_free(fc_vars);
	}

	/*
	 * 检查合并连接的可能性。 这通常会成功，因为操作来自 EquivalenceClass；
	 * 但我们可能会将原始子句简化为常量。
	 */
	fc_check_mergejoinable(fc_restrictinfo);

	/*
	 * 请注意，我们不对 initialize_mergeclause_eclasses() 进行调用；
	 * 调用者可以比我们更便宜地处理这个。 在这之前调用 distribute_restrictinfo_to_rels() 是可以的。
	 */

	/*
	 * 将新子句推入所有适当的 restrictinfo 列表中。
	 */
	distribute_restrictinfo_to_rels(fc_root, fc_restrictinfo);

	return fc_restrictinfo;
}

/*
 * build_implied_join_equality --- 为派生的等式构建一个 RestrictInfo
 *
 * 此功能与 process_implied_equality() 重叠，但我们不能将 RestrictInfo
 * 推入 joininfo 树中。
 *
 * 请注意：此函数将复制 item1 和 item2，但确保 Relids 参数是新副本，而不是与其他用途共享的工作是调用者的责任。
 *
 * 注意：我们在这里不进行 initialize_mergeclause_eclasses()。确认 left_ec/right_ec 设定为必需是调用者的责任。
 */
RestrictInfo * build_implied_join_equality(PlannerInfo *fc_root,
							Oid fc_opno,
							Oid fc_collation,
							Expr *fc_item1,
							Expr *fc_item2,
							Relids fc_qualscope,
							Relids fc_nullable_relids,
							Index fc_security_level)
{
	RestrictInfo *fc_restrictinfo;
	Expr	   *fc_clause;

	
/*
	 * 构建新子句。复制以确保其与原始子句没有共享的子结构（这是必要的，以防其中有子查询...）
	 */
	fc_clause = make_opclause(fc_opno,
						   BOOLOID, /* opresulttype */
						   false,	/* opretset */
						   copyObject(fc_item1),
						   copyObject(fc_item2),
						   InvalidOid,
						   fc_collation);

	/*
	 * 构建 RestrictInfo 节点本身。
	 */
	fc_restrictinfo = make_restrictinfo(fc_root,
									 fc_clause,
									 true,	/* is_pushed_down */
									 false, /* outerjoin_delayed */
									 false, /* pseudoconstant */
									 fc_security_level,	/* security_level */
									 fc_qualscope, /* required_relids */
									 NULL,	/* outer_relids */
									 fc_nullable_relids);	/* nullable_relids */

	/* 设置 mergejoinability/hashjoinability 标志 */
	fc_check_mergejoinable(fc_restrictinfo);
	fc_check_hashjoinable(fc_restrictinfo);
	fc_check_memoizable(fc_restrictinfo);

	return fc_restrictinfo;
}


/*
 * match_foreign_keys_to_quals
 *		将外键约束与等价类和连接条件匹配
 *
 * 这里的想法是查看哪些查询连接条件与外键关系的等式约束匹配。
 * 对于此类连接条件，我们可以使用 FK 语义进行更可靠的选择性估计，
 * 这比从统计数据中估计要好，特别是对于多列 FK，通常假设条件独立的情况往往不成立。
 *
 * 在此函数中，我们在 root->fkey_list 中注释 ForeignKeyOptInfos，
 * 通过与哪些等价类和连接条件子句匹配的信息，并丢弃与查询无关的 ForeignKeyOptInfos。
 */
void match_foreign_keys_to_quals(PlannerInfo *fc_root)
{
	List	   *fc_newlist = NIL;
	ListCell   *fc_lc;

	foreach(fc_lc, fc_root->fkey_list)
	{
		ForeignKeyOptInfo *fc_fkinfo = (ForeignKeyOptInfo *) lfirst(fc_lc);
		RelOptInfo *fc_con_rel;
		RelOptInfo *fc_ref_rel;
		int			fc_colno;

		/*
		 * 任何一个 relid 可能指向查询的 rtable 中的一个 rel，
		 * 但未通过 jointree 引用，因此不会有 RelOptInfo。 因此，
		 * 此处不要使用 find_base_rel()。 我们可以忽略这样的 FKs。
		 */
		if (fc_fkinfo->con_relid >= fc_root->simple_rel_array_size ||
			fc_fkinfo->ref_relid >= fc_root->simple_rel_array_size)
			continue;			/* 只是 parano */
		fc_con_rel = fc_root->simple_rel_array[fc_fkinfo->con_relid];
		if (fc_con_rel == NULL)
			continue;
		fc_ref_rel = fc_root->simple_rel_array[fc_fkinfo->ref_relid];
		if (fc_ref_rel == NULL)
			continue;

		/*
		 * 除非两个 rel 都是 baserels，否则忽略 FK。 这将消除那些链接到继承子关系（otherrels）
		 * 和因连接移除而移除的关系（死关系）的 FKs。
		 */
		if (fc_con_rel->reloptkind != RELOPT_BASEREL ||
			fc_ref_rel->reloptkind != RELOPT_BASEREL)
			continue;

		/*
		 * 扫描列并尝试将其与等价类和条件匹配。
		 *
		 * 注意：对于简单的内连接，任何匹配都应该在一个等价类中。
		 * 语法上与 FK 等式匹配的“松散”条件必须因为它们是外连接条件或类似条件而被拒绝为 EC 状态。
		 * 如果它们不是 outerjoin_delayed，我们仍然可以考虑它们匹配 FK。
		 */
		for (fc_colno = 0; fc_colno < fc_fkinfo->nkeys; fc_colno++)
		{
			EquivalenceClass *fc_ec;
			AttrNumber	fc_con_attno,
						fc_ref_attno;
			Oid			fc_fpeqop;
			ListCell   *fc_lc2;

			fc_ec = match_eclasses_to_foreign_key_col(fc_root, fc_fkinfo, fc_colno);
			/* 如果我们获得了 EC 匹配，就不必查找松散条件 */
			if (fc_ec != NULL)
			{
				fc_fkinfo->nmatched_ec++;
				if (fc_ec->ec_has_const)
					fc_fkinfo->nconst_ec++;
				continue;
			}

			/*
			 * 扫描 joininfo 列表以获取相关条件。 任何一个 rel 的 joininfo
			 * 列表同样有效；我们使用 con_rel 的。
			 */
			fc_con_attno = fc_fkinfo->conkey[fc_colno];
			fc_ref_attno = fc_fkinfo->confkey[fc_colno];
			fc_fpeqop = InvalidOid;	/* 只有在需要时我们才会查找此项 */

			foreach(fc_lc2, fc_con_rel->joininfo)
			{
				RestrictInfo *fc_rinfo = (RestrictInfo *) lfirst(fc_lc2);
				OpExpr	   *fc_clause = (OpExpr *) fc_rinfo->clause;
				Var		   *fc_leftvar;
				Var		   *fc_rightvar;

				/* 忽略 outerjoin-delayed 条件 */
				if (fc_rinfo->outerjoin_delayed)
					continue;

				/* 仅二元 OpExprs 对考虑是有用的 */
				if (!IsA(fc_clause, OpExpr) ||
					list_length(fc_clause->args) != 2)
					continue;
				fc_leftvar = (Var *) get_leftop((Expr *) fc_clause);
				fc_rightvar = (Var *) get_rightop((Expr *) fc_clause);

				/* 操作数必须是变量，可能具有 RelabelType */
				while (fc_leftvar && IsA(fc_leftvar, RelabelType))
					fc_leftvar = (Var *) ((RelabelType *) fc_leftvar)->arg;
				if (!(fc_leftvar && IsA(fc_leftvar, Var)))
					continue;
				while (fc_rightvar && IsA(fc_rightvar, RelabelType))
					fc_rightvar = (Var *) ((RelabelType *) fc_rightvar)->arg;
				if (!(fc_rightvar && IsA(fc_rightvar, Var)))
					continue;

				/* 现在尝试将变量与当前外键列匹配 */
				if (fc_fkinfo->ref_relid == fc_leftvar->varno &&
					fc_ref_attno == fc_leftvar->varattno &&
					fc_fkinfo->con_relid == fc_rightvar->varno &&
					fc_con_attno == fc_rightvar->varattno)
				{
					/* 变量匹配，但是否是正确的操作符？ */
					if (fc_clause->opno == fc_fkinfo->conpfeqop[fc_colno])
					{
						fc_fkinfo->rinfos[fc_colno] = lappend(fc_fkinfo->rinfos[fc_colno],
														fc_rinfo);
						fc_fkinfo->nmatched_ri++;
					}
				}
				else if (fc_fkinfo->ref_relid == fc_rightvar->varno &&
						 fc_ref_attno == fc_rightvar->varattno &&
						 fc_fkinfo->con_relid == fc_leftvar->varno &&
						 fc_con_attno == fc_leftvar->varattno)
				{
					/*
					 * 反向匹配，必须检查交换子操作符。 如果我们还没有查找它，请查找它。
					 * （在最坏的情况下，我们可能在此处进行多次查找，但这将需要一个没有交换子操作符的 FK 等式，
					 * 这是不太可能的。）
					 */
					if (!OidIsValid(fc_fpeqop))
						fc_fpeqop = get_commutator(fc_fkinfo->conpfeqop[fc_colno]);
					if (fc_clause->opno == fc_fpeqop)
					{
						fc_fkinfo->rinfos[fc_colno] = lappend(fc_fkinfo->rinfos[fc_colno],
														fc_rinfo);
						fc_fkinfo->nmatched_ri++;
					}
				}
			}
			/* 如果我们找到任何匹配的松散条件，则计数列为匹配 */
			if (fc_fkinfo->rinfos[fc_colno])
				fc_fkinfo->nmatched_rcols++;
		}

		
		if ((fc_fkinfo->nmatched_ec + fc_fkinfo->nmatched_rcols) == fc_fkinfo->nkeys)
			fc_newlist = lappend(fc_newlist, fc_fkinfo);
	}
	/*替换fkey_list，从而丢弃任何无用的条目*/
	fc_root->fkey_list = fc_newlist;
}


/*****************************************************************************
 *
 *	 检查可合并连接和哈希连接条款
 *
 *****************************************************************************/

/*
 * check_mergejoinable
 *	  如果restrictinfo的条款是可合并连接的，则在restrictinfo中设置
 *	  合并连接信息字段。
 *
 *	  目前，我们支持对二元操作条款执行合并连接，其中操作符是
 *	  可合并连接的操作符。参数可以是任何东西——只要它们中没有
 *	  持久性函数。
 */
static void fc_check_mergejoinable(RestrictInfo *fc_restrictinfo)
{
	Expr	   *fc_clause = fc_restrictinfo->clause;
	Oid			fc_opno;
	Node	   *fc_leftarg;

	if (fc_restrictinfo->pseudoconstant)
		return;
	if (!is_opclause(fc_clause))
		return;
	if (list_length(((OpExpr *) fc_clause)->args) != 2)
		return;

	fc_opno = ((OpExpr *) fc_clause)->opno;
	fc_leftarg = linitial(((OpExpr *) fc_clause)->args);

	if (op_mergejoinable(fc_opno, exprType(fc_leftarg)) &&
		!contain_volatile_functions((Node *) fc_restrictinfo))
		fc_restrictinfo->mergeopfamilies = get_mergejoin_opfamilies(fc_opno);

	/*
	 * 注意：op_mergejoinable只是一个提示；如果我们未能在任何
	 * btree操作族中找到该操作符，mergeopfamilies将保持为空，因此
	 * 该条款不会被视为可合并连接。
	 */
}

/*
 * check_hashjoinable
 *	  如果restrictinfo的条款是可哈希连接的，则在restrictinfo中设置
 *	  哈希连接信息字段。
 *
 *	  目前，我们支持对二元操作条款执行哈希连接，其中操作符是
 *	  可哈希连接的操作符。参数可以是任何东西——只要它们中没有
 *	  持久性函数。
 */
static void fc_check_hashjoinable(RestrictInfo *fc_restrictinfo)
{
	Expr	   *fc_clause = fc_restrictinfo->clause;
	Oid			fc_opno;
	Node	   *fc_leftarg;

	if (fc_restrictinfo->pseudoconstant)
		return;
	if (!is_opclause(fc_clause))
		return;
	if (list_length(((OpExpr *) fc_clause)->args) != 2)
		return;

	fc_opno = ((OpExpr *) fc_clause)->opno;
	fc_leftarg = linitial(((OpExpr *) fc_clause)->args);

	if (op_hashjoinable(fc_opno, exprType(fc_leftarg)) &&
		!contain_volatile_functions((Node *) fc_restrictinfo))
		fc_restrictinfo->hashjoinoperator = fc_opno;
}

/*
 * check_memoizable
 *	  如果restrictinfo的条款适合用于Memoize节点，
 *	  将lefthasheqoperator和righthasheqoperator设置为在缓存时所需的
 *	  哈希相等操作符。
 */
static void fc_check_memoizable(RestrictInfo *fc_restrictinfo)
{
	TypeCacheEntry *fc_typentry;
	Expr	   *fc_clause = fc_restrictinfo->clause;
	Oid			fc_lefttype;
	Oid			fc_righttype;

	if (fc_restrictinfo->pseudoconstant)
		return;
	if (!is_opclause(fc_clause))
		return;
	if (list_length(((OpExpr *) fc_clause)->args) != 2)
		return;

	fc_lefttype = exprType(linitial(((OpExpr *) fc_clause)->args));

	fc_typentry = lookup_type_cache(fc_lefttype, TYPECACHE_HASH_PROC |
								 TYPECACHE_EQ_OPR);

	if (OidIsValid(fc_typentry->hash_proc) && OidIsValid(fc_typentry->eq_opr))
		fc_restrictinfo->left_hasheqoperator = fc_typentry->eq_opr;

	fc_righttype = exprType(lsecond(((OpExpr *) fc_clause)->args));

	/*
	 * 查找正确的类型，除非它与左侧类型相同，在这种情况下typentry
	 * 已经指向所需的TypeCacheEntry。
	 */
	if (fc_lefttype != fc_righttype)
		fc_typentry = lookup_type_cache(fc_righttype, TYPECACHE_HASH_PROC |
									 TYPECACHE_EQ_OPR);

	if (OidIsValid(fc_typentry->hash_proc) && OidIsValid(fc_typentry->eq_opr))
		fc_restrictinfo->right_hasheqoperator = fc_typentry->eq_opr;
}
